# Who said that? 2016 Presidential election

In this challenge, your task is to make write a program with less than 300 characters that takes a short paragraph or a few sentences that a candidate has said and output who said it.

Input: Can be taken as a parameter to a function, input to a program etc. It will be a short paragraph, properly punctuated.

Output: The candidate that you think it is. This could be one of

Ben Carson (1)
Ted Cruz (2)
John Kasich (3)
Marco Rubio (4)
Donald Trump (5)
Hillary Clinton (6)
Bernie Sanders (7)


I've left off the names of people who have dropped out as of March 1st. You may output the name itself, or, more conveniently, the number that corresponds to the name.

Scoring: Your score is the percentage of test cases you get right. Highest score wins. Ties (or perfect scores) are broken by code length as in a code golf.

The test cases can be pulled from:

http://www.presidency.ucsb.edu/debates.php

Click on each debate, both Democrat and Republican that has happened so far (before March 1st). Every paragraph is a test case, unless the "paragraph" is less than 20 characters long.

Here is code that pulls out the test cases from a particular page:

var t = \$(".tools").parentNode.querySelectorAll("p");
var categ = {}, cur = 0;
for (var i = 0; i < t.length; ++i) {
var p = t[i], str = p.innerText;
if (p.querySelector("b")) {
cur = p.querySelector("b").innerText.replace(':', '');
str = str.replace(/^.*?:\s/, '');
}
str = str.replace(/$applause$/g, '')
if (str.length < 20) continue;
if (categ[cur] == null) categ[cur] = [];
categ[cur].push(str);
}


You can then do categ.SANDERS to get a list of all the paragraphs that Senator Sanders has said.

You can discard anything that isn't said by the candidates listed above (e.g. categ.BUSH or categ.CHRISTIE).

Here is the file with all the test cases: https://drive.google.com/file/d/0BxMn8--P71I-bDZBS2VZMDdmQ28/view?usp=sharing

The file is organized by candidate

CANDIDATE CANDIDATE_LAST_NAME
(empty line)
Series of statements. Each paragraph is separated by (NEW PARAGRAPH)-
(empty line)
CANDIDATE NEXT_CANDIDATE_LAST_NAME
(empty line)
etc.


An example partial submission would be:

if (/ win | wall | great | beautiful/.test(p)) return 5;
if (/ percent | top one | rigged /.test(p)) return 7;
// etc. for all candidates


or

var words = p.split(' ');
// majority of words have less than 5 characters
if (words.length - words.filter(a => a.length < 5).length < 4) evidence[5]++;
// at the end
return /* index with the most evidence */


Here is a place where you can test javascript solutions: https://jsfiddle.net/prankol57/abfuhxrh/

The code uses the parameter p to represent the phrase to classify. Example code that scores around 20% (guessing would get around 11%):

if (/ rigged | top | percent | Wall Street /.test(p)) return 'Sanders';
return 'Trump';


Exactly what I'm asking: Write a program/function in less than 300 characters that takes as input a phrase that a candidate has said and returns as output which candidate said it. Your score is the percentage of test cases you get right. Highest score wins.

Yes, I know that a lot of lines have [laughter] or [cheering] in them. These will not be removed. At worst, they are extra information you can ignore; at best, they are extra information you can use (e.g. I made this up, but maybe people laughter is evidence that Marco Rubio is speaking). The test cases are as they appear in the text file.

• I have a suggestion. How about you make it code-golf, but you have to get all the quotes right? Also, you might want to make the quotes a lot shorter, as this is a little ridiculous to solve as-is. – Cyoce Mar 2 '16 at 3:07
• @Cyoce getting all the quotes right would be ridiculous (I think) considering the sheer number of quotes. – soktinpk Mar 2 '16 at 3:13
• Clever challenge idea, may need some refining though. Have you considered posting in Sandbox for some feedback? – Ashwin Gupta Mar 2 '16 at 6:19
• What is the winning criterion? (And why do you think that no-one will get a perfect score?) – Peter Taylor Mar 2 '16 at 11:00
• The source data you provided is a little messy (difficult to parse automatically), which I think takes away some of the spirit of the challenge. I've made a cleaned-up version which uses one line per quote, with a blank line separating the next candiate name. This is much easier to parse in most languages. I've uploaded it here: drive.google.com/file/d/0B3uyVnkMpqbVSnVrZkVwTUhDODg (other than changing newlines, I've left the data untouched. That includes what looks like an encoding issue for –) – Dave Mar 7 '16 at 19:09

# Polyglot, ~18.6%

This works in: Cjam, Pyth, TeaScript, Japt, Seriously, 05AB1E, GolfScript, Jelly, and probably many more.

6


This outputs Hillary for all inputs. This is because Hillary said the most. While it's not the most ingenious way to do this. It works ¯\_(ツ)_/¯

• I like how this gets flagged as low quality post. :P – Denker Mar 5 '16 at 1:24
• @DenkerAffe probably for being short – Downgoat Mar 5 '16 at 1:27
• Any reason for using JavaScript? You could've golfed it down to one character in some other language :P – ghosts_in_the_code Mar 7 '16 at 7:14
• @ghosts_in_the_code fixed – Downgoat Mar 7 '16 at 22:58

## Pyth, 34.16% (297 bytes)

FNc"7creta
6enato
3ohio
2donal
7 major
6o try t
5tot
5se me
7nai
4m pres
2he ob
3 bala
5jeb
6e aff
5mendous
2mnest
5. we'r
7ave got to
2c ter
4ntur
7 campaign
2flat
5obo
4is pre
4-here'
2note
2m el
4 issue
5, very
6o af
1fact o
6en's
5pany
6he republicans
7 --
4meon
5bea
4ory o
7"bI}tNrzZhNB


(note that some lines end in spaces)

I've gone with the simplest option I could think of: check a list of patterns, and as soon as you find a match, output the corresponding candidate. If all else fails, output the most likely candidate from the remainder. After that, it's all about cramming as much data into 300 bytes as possible.

FNc"<data>"bI}tNrzZhNB


Breakdown:

FN                      for N in ...
"<data>"              the hard-coded data (newline separated)
c                      split using...
b             '\n' constant,
I           if
tN         tail (all but first char) of current item
}           is contained within
rzZ      the input (lowercased),
then:
hN    print the head (first char) of the current item
B   and break out of the loop.


So where does that data come from? Well the structure is simply:

<candidate_number><phrase>
<candidate_number><phrase>
<etc.>


(with an entry at the end with no phrase to act as the final fall-back)

But why those particular items? I wrote a C++ program to analyse the provided dataset (with some manual cleaning of newlines first to make the structure consistent). It looks at all substrings ("tokens") in each quote (1-16 characters), then repeatedly checks for the token which gives the most benefit to go next in the list. Once a pattern is in the list, remove any quotes which match it and repeat (it gets a bit more complicated to keep it fast but that's the basics). The code is probably too long to include here, but I might put it on github later (when I've cleaned it up a little).

I tried a couple of scoring systems. In the end I went with this one:

score = (
+ matching_quote_count_for_most_likely_author * 10
- matching_quote_count_for_other_authors * 7
- token_length
)


A stricter approach of only allowing new items which don't introduce incorrect answers seemed to get stuck at about 20-25%, needing a lot of patterns to get higher. This fuzzier approach does much better, and can still reach ~80% accuracy (with 550 items). The submitted score has 38 items, which was the most I could fit in the 300 character limit.

The 34% result actually comes from a test C++ program which performs the same steps. It should match, but I haven't got a Pyth test harness to check it with.

This is the first time I've used Pyth, so I imagine some more bytes could be squeazed out, allowing a bit more data.

• Also I now know that Sanders loves talking about secretary Clinton, Clinton's obsessed with senator Sanders, Kasich loves Ohio, Cruz is always mentioning Donald Trump, Rubio is obsessed with Centuries, Carson has all the "fact[s] of the matter", and Trump totally loves saying "totally". This feels like the beginnings of a politics-bingo generator. I'll have to try it on some UK personalities… – Dave Mar 7 '16 at 19:02
• I think you could save some bytes here by packing the string with .". – lirtosiast Mar 14 '16 at 16:59

# Javascript, 32.87%

299 Characters:

function Q(a,b){return p.toLowerCase().split(' ').join('').includes(a)<<b}z=Q('ink',0)+Q('int',1)+Q('ona',2)+Q('rica',3)+Q('twe',4)+Q("we'",5)+Q('youkn',6);return '55472726464727446676664676767676563641233643334456364233336141745116222136477126111113361611262316263122216111673336225611363276'[z]*1


Strategy:

I did a bruce force search on which word segments to include in a "hash". Then a string lookup happens with that hash in such a way that chooses the most likely candidate for that hash.

The code itself:

// The Q function checks if a string is present.
// Then left-shifts the true/false result up to e.g. 64,32,16,8,4,2,1
// This way we can combine results into any number 0 to 127.
function Q(a,b){return p.toLowerCase().split(' ').join('').includes(a)<<b}

// Now we check for key string occurrences:
z=Q('ink',0)+Q('int',1)+Q('ona',2)+Q('rica',3)+Q('twe',4)+Q("we'",5)+Q('youkn',6)

// Finally, use this as an index into the lookup string. (Multiply by 1 to convert char to int.)
return '55472726464727446676664676767676563641233643334456364233336141745116222136477126111113361611262316263122216111673336225611363276'[z]*1


This is my first ever code golf submission, so suggestions are welcome :)

## Mathematica, 23.7775 %

(l=ToLowerCase@#;Ordering[-StringCount[l,#]&/@{"fact","donald"|"obama","done"|"ohio","issue"|"united"|"why"|"world","great"|"many","senator","american"|"believe"|"campaign"|"secretary"|"street"|"wall"},1])[[1]]&


It counts the occurences of common keywords unique to each candidate and outputs the number of the candidate with the highest score.

Basically, I found the commonest words of all candidates

t = Import["~/Documents/candidate quotes.txt"];
ts = DeleteCases[StringSplit[t, "\n\n"], ""];
tss = Split[ts, StringLength[#2] > 20 &][[{3, 4, 5, 6, 7, 1, 2}]];
names = StringSplit[#][[2]] & /@ tss[[All, 1]];
quotes = StringSplit[#, "(NEXT PARAGRAPH)"] & /@ StringJoin /@ tss[[All, 2 ;;]];
(* remove the 100 commonest english words *)
wd = WikipediaData["Most common words in English", "ArticleWikicode"];
Flatten[StringSplit[StringCases[wd,
Shortest["{| class=\"wikitable\"" ~~ w__ ~~ "}"] -> w], "\n"]];
common100 =
Alternatives @@ ToLowerCase@DeleteDuplicates@Flatten[StringSplit /@
StringCases[#,
"|| " ~~ ("[[" | "") ~~ w : ((WordCharacter | " ") ..) ->
w] & /@ %];
commonest =
Commonest[
Flatten[StringSplit[
StringDelete[ToLowerCase[#],
PunctuationCharacter | (WordBoundary ~~ (common100) ~~
WordBoundary)]] & /@ #], 20] & /@ quotes;


and chose the common keywords that are unique for each candidate.

keywords =
Alternatives @@@
Table[Complement[commonest[[n]],
Union[Flatten[Delete[commonest, n]]]], {n, Length[names]}];


After manually deleting some of the keywords, this is the final table:

Carson    fact
Cruz      donald|obama
Kasich    done|ohio
Rubio     issue|united|why|world
Trump     great|many
Clinton   senator
Sanders   american|believe|campaign|secretary|street|wall


With these keywords the total function length is 211 characters. I tested the function over all quotes:

pairs = Flatten[MapThread[Table[q -> #1, {q, #2}] &, {names, quotes}]];
test[q_ -> n_] := Boole[n === names[[p@q]]] (* here p is my function that outputs the predicted candidate's number *)
Total[ParallelMap[test, pairs]]/Length[pairs] // N


which gives an accuracy of 23.7775 %.

# Python, 25.677868%

Arbitrarily picked four different characters that would be used to identify the candidates. Each candidate is given a score factor per character based on a hill climbing search that I ran for a few minutes to end up at 25.68%.

I suppose that this at least proves that the concept is better than picking a candidate blindfolded or just picking Clinton, but I would be interested in seeing someone apply a better search algorithm, both for the factors and for the characters used.

w=dict(zip("hr?.",((.847,.491,.821,.54,.744,.765,.234),(.494,.777,.202,.587,.7,.852,.484),(.915,.187,.161,.559,.748,.244,.43),(.11,.013,.628,.974,1.037,.484,.302))))
def f(t,r=(0,0,0,0,0,0,0)):
s=r
for c in t:s=map(lambda a,b:a+b,s,w.get(c,r))
return s.index(max(s))+1


# Javascript, TBD

a=[...p].reduce((a,b)=>(a<<5)-a+b.charCodeAt(0)|0,0)%1000,alert(a>0?0:1000,a<79?1:a<226?2:a<333?3:a<497?4:a<697?5:a<849?6:7)


Converts each string to a hash code, then uses probabilistic methods to determine the speaker. Would be nice if someone with a good setup could test this for me.

• I count about 16.1%, but I'm not really sure what it does. What does the a+=a?0:1000 do? (I had to replace the alert with a return so I wasn't sure exactly what to do) – soktinpk Mar 6 '16 at 21:41
• @soktinpk Sorry, the a+= must have been a typo. – LegionMammal978 Mar 6 '16 at 22:07