Let's play Hangman?
Python3, 299 Bytes
import sys,collections as c;x,X,N,*Z=sys.argv;print([x for x in c.Counter(''.join([''.join(x)for x in map(set,filter(lambda l:len(l)==int(X)+1and all(x not in X.lower()for x in l)and all(l[int(x[0])-1]==x[1].lower()for x in Z),open('wordlist.txt')))]))if x not in ''.join(Z).lower()and x!='\n'][0])
pretty sure this can be golfed further.
Filters the wordlist for potential matches, builds a char frequency map, and selects the most often occuring character which is not yet picked.
PowerShell, 248 246 241 bytes
$a=$args;$c=$a[0];$b=$a[2..$c];((gc wordlist.txt)-match"^$((1..$c|%{$e=,"[^$($a[1])]"*$c}{$e[$_-1]=(((@($b)-match"^$_\D")[0]-split$_)[-1],$e[$_-1]-ne'')[0]}{$e-join''}))$"-join''-split'\B'|?{!(@($b)-match$_).Count}|group|sort count)[-1].Name
Ungolfed
Well, as much as I could without changing the way it works:
$a=$args
$c=$a[0]
$b=$a[2..$c]
(
(gc wordlist.txt) -match "^$(
(
1..$c | ForEach-Object -Begin {
$e = ,"[^$($a[1])]" * $c
} -Process {
$e[$_-1] = (
( ( @($b) -match "^$_\D" )[0] -split $_ )[-1] , $e[$_-1] -ne ''
)[0]
} -End {
$e-join''
}
)
)$" -join '' -split'\B' |
Where-Object {
-not (@($b) -match $_).Count
} |
Group-Object |
Sort-Object count
)[-1].Name
Breakdown
The approach I took here was to first generate a regular expression to get possible words out of the word list. Since I know the length of the word, and the letters that didn't work, I can make a regex out of that fairly easily.
So in the PEOPLE example, 6 letters with XBZ not being part of the word, I would look to generate ^PE[^XBZ]P[^XBZ]E$
.
I'm exploiting the fact that Get-Content
(gc
) returns an array of lines, and the -match
operator when used with an array on the left side, returns an array of matches instead of a bool, so I can quickly get a list of just words that are candidates, once I have the regex.
To generate the regex, I start with an array ($e
) of the negative matching character class with $c
elements ($c
being the number of letters in the word). Iterating through the numbers 1 through $c
, I check for a matched letter at that position, and if it exists, I replace the element in $e
with that letter.
Once I've iterated through all of the positions, the final array is -join
ed (with empty string) and we have our regex.
So now I've got an array of all the possible words it could be. A quick -join
with empty string on that, gives me one big concatenated string of all the words, the I split on \B
(not a word boundary, if I split on empty string I will get 2 extra blank elements), so now I have an array of every letter in every possible word.
Piping that into Where-Object
lets me filter out the letters that have already been matched. This part was a real pain. It had to deal with the list of matched letters (which include the position) being 1 element, more than 1 element, or 0 elements, hence forcing $b
into an array first so that -match
can operate on all them, but that (unfortunately in this case) returns an array, so we have to check .Count
. Using !(thing).Count
is a bit smaller than using (thing).Count-gt0
.
Moving on, now we've got an array of all the single characters (as string
s not as char
s) from all of the words it could possibly be, minus the letters that were already guessed correctly.
Piping that into Group-Object
gives me an object with the counts of each letter, so a quick pipe into Sort-Object count
makes it easy to get the highest count. Rather than do (thing|sort count -des)[0]
we can use (thing|sort count)[-1]
. In PowerShell [-1]
gets the last element. At this point we're still dealing with the objects that came from Group-Object
so we get the .Name
property which is the letter that appears the most.
Notes
- Should work with PowerShell v3+; almost certainly will choke on 2.
- Remember when you call a PowerShell script, pass arguments with spaces, not commas.
- Although I didn't see it in the rules, it looks like everyone is using the filename
wordlist.txt
otherwise that could shave a few bytes off. - Speed shouldn't be a problem. This appears to run instantly for me. The slowest run I could get it to do (
.\hangman.ps1 7 0
) runs in about 350ms.
Java, 646 640 631 607 606 (short) 790 789 779 (fast) bytes
SHORT
import java.util.*;class I{public static void main(String[]a)throws Exception{char[]w=a[1].toCharArray(),p,q;int l=Integer.parseInt(a[0]),i,z=w.length,j;q=new char[l];for(i=2;i<a.length;i++)q[Character.getNumericValue(a[i].charAt(0))-1]=(char)(a[i].charAt(1)+32);java.io.File u=new java.io.File("wordlist.txt");Scanner s=new Scanner(u);while(s.hasNextLine()){p=s.nextLine().toCharArray();if(p.length==l)for(i=0;i<l;i++)if(p[i]==q[i]||q[i]=='\0'){if(i==l-1)y:for(i=0;i<l;i++)for(j=0;j<z;j++)if(!(p[i]==w[j])){if(j==z-1){System.out.print(p[new String(q).indexOf('\0')]);return;}}else break y;}else{break;}}}}
FAST
import java.util.*;class I{public static void main(String[]a)throws Exception{char[]w=a[1].toCharArray(),p,q;int l=Integer.parseInt(a[0]),i,z=w.length,j,k,o,n[]=new int[255],r[];q=new char[l];for(i=2;i<a.length;i++)q[Character.getNumericValue(a[i].charAt(0))-1]=(char)(a[i].charAt(1)+32);String m=new String(q);java.io.File u=new java.io.File("wordlist.txt");Scanner s=new Scanner(u);while(s.hasNextLine()){p=s.nextLine().toCharArray();h:if(p.length==l)for(i=0;i<l;i++)if(p[i]==q[i]||q[i]=='\0'){if(i==l-1)y:for(i=0;i<l;i++)for(j=0;j<z;j++)if(p[i]!=w[j]){if(j==z-1){for(k=0;k<l-m.replace("\0","").length();k++)n[(int)p[new String(q).indexOf('\0',k)]]++;break h;}}else break y;}else{break;}}r=n.clone();Arrays.sort(n);for(o=0;o<255;o++)System.out.print(r[o]==n[254]?(char)o:"");}}
Put the wordlist file in the folder.
Short version algorithm
- Load Args
- Construct the word we are trying to guess {'p','e','\0','p','\0','e'}
- Load WordList
- Go through each line of WordList
- Stop when you find that the whole word matches this condition
p[i] == q[i] || q[i] == '\0'
where p is a word from the wordlist (char array), and q is the word we are trying to guess - Loop through wrong chars and compare to the word
- Print the first missing character
Long version algorithm
- Short steps 1-7
- Increment the character count in the n array for the missing chars
- Loop until all the words come through
- Print the character that had the highest count