Wordle solver including sorting scoring method for matching words
by Håkan Kjellerstrand (hakank@gmail.com)
http://hakank.org/
by Håkan Kjellerstrand (hakank@gmail.com)
http://hakank.org/
About the first thing in the morning for now 109 days, I solve the Wordle puzzle (https://www.nytimes.com/games/wordle/index.html). After manually doing this for a few days I realized that it took much time, so I started to figure out the best start word and then also wrote a Wordle solver.
This solver was - however - done in another programming languages. Here is a port of the solver to Wolfram Language.
This solver was - however - done in another programming languages. Here is a port of the solver to Wolfram Language.
Syntax and usage
Syntax and usage
The constraints
The constraints
The Wordle solver uses the following approach:
It first figure out what words are allowed based of three filters (constraints):
* correct position: characters in correct position. These are the Green characters in the Wordle hints.
* correct chars: characters in the word but in incorrect position. These are the Brown hint characters.
* chars not in the word. These are the Gray characters.
The call is
wordle[correctPos,correctChars,notInWord, words]
with the following structure.
wordle[“.....”,{“”,””,””,””,””},””,words]
Example:
wordle[{“i.e..”},{“”,””,””,”n”,”nr”},”pach”,words]
* correct position: We know that the first character is “i” and the third is “e”.
* correct chars: “n” is in the word but not in the the fourth or fifth position. And r in in the word but not in the fifth position
* chars not in word: Neither of characters “p”, “a”, “c”, nor “h” are in the word.
According to the used wordlist, only the word “inert” satisfies these three constraints.
A little more interesting example is the following:
wordle[“.r.n.”,{“”,””,””,””,””},”slatcoe”,words]
Here is a list of the words that match:
{bring,brink,briny,drink,drunk,grind,wring,wrung}
This list is in plain alphabetical order, but we don’t get a hint which word is the best to pick.
Which of these word should we pick?
It first figure out what words are allowed based of three filters (constraints):
* correct position: characters in correct position. These are the Green characters in the Wordle hints.
* correct chars: characters in the word but in incorrect position. These are the Brown hint characters.
* chars not in the word. These are the Gray characters.
The call is
wordle[correctPos,correctChars,notInWord, words]
with the following structure.
wordle[“.....”,{“”,””,””,””,””},””,words]
Example:
wordle[{“i.e..”},{“”,””,””,”n”,”nr”},”pach”,words]
* correct position: We know that the first character is “i” and the third is “e”.
* correct chars: “n” is in the word but not in the the fourth or fifth position. And r in in the word but not in the fifth position
* chars not in word: Neither of characters “p”, “a”, “c”, nor “h” are in the word.
According to the used wordlist, only the word “inert” satisfies these three constraints.
A little more interesting example is the following:
wordle[“.r.n.”,{“”,””,””,””,””},”slatcoe”,words]
Here is a list of the words that match:
{bring,brink,briny,drink,drunk,grind,wring,wrung}
This list is in plain alphabetical order, but we don’t get a hint which word is the best to pick.
Which of these word should we pick?
The scoring (sorting) method
The scoring (sorting) method
This is where the second part of the system comes into play: the sorting score method. This part might be of some contribution (I haven’t seen another Wordle solver using exactly this method).
The ideas is to prefer words that are "as common as possible" which is here translated into sorting the words according to how common the characters are in each position of the words . “Common” here simply means according to frequencies using some wordlist. The section "Frequency tables for the scoring" below calculates the scores.
Here's the table of these frequencies . Note that it’s reversed according to the frequency :
xzyjkquinovhewlrmdgfaptbcs position 1
jzqfkgxvbsdymcwptnhulieroa position 2
qjhzkxfwyvcbpmgdstlnrueoia position 3
qjxyzbwhfvpkmdguotcrilasne position 4
jqvuzxbiwfcsgmpoakdnhlrtye position 5
It shows, for example, that for the first position the character "s" is the most common, then "c", then "b", etc ; for the last position of the word “e” is the most common character. The list is reversed since the score of a word is simply the total of the positions in these strings for the word’s characters . We also throw in a rule that prefers words with unique characters : If the word has 5 distinct characters then it get 100 extra points, otherwise no extra points .
The words above is sorted according to the scoring method into the following list :
{briny, brink, grind, bring, drink, drunk, wring, wrung}
The word to pick is thus: briny
The ideas is to prefer words that are "as common as possible" which is here translated into sorting the words according to how common the characters are in each position of the words . “Common” here simply means according to frequencies using some wordlist. The section "Frequency tables for the scoring" below calculates the scores.
Here's the table of these frequencies . Note that it’s reversed according to the frequency :
xzyjkquinovhewlrmdgfaptbcs position 1
jzqfkgxvbsdymcwptnhulieroa position 2
qjhzkxfwyvcbpmgdstlnrueoia position 3
qjxyzbwhfvpkmdguotcrilasne position 4
jqvuzxbiwfcsgmpoakdnhlrtye position 5
It shows, for example, that for the first position the character "s" is the most common, then "c", then "b", etc ; for the last position of the word “e” is the most common character. The list is reversed since the score of a word is simply the total of the positions in these strings for the word’s characters . We also throw in a rule that prefers words with unique characters : If the word has 5 distinct characters then it get 100 extra points, otherwise no extra points .
The words above is sorted according to the scoring method into the following list :
{briny, brink, grind, bring, drink, drunk, wring, wrung}
The word to pick is thus: briny
All wordlist words sorted according to the scoring method, and the first guess word
All wordlist words sorted according to the scoring method, and the first guess word
Here are all words in the wordlist sorted according to the score method, using the empty call
wordle[“.....”, {“”, “”, “”, “”, “”}, “”, words]
{saint, crane, crone, coast, brine, crony, cause, briny, boast, slant, poise,
paint, shine, saner, prone, point, count, corny, clone,
<<2277>>,
occur, known, humus, hippo, fuzzy, dizzy, kappa, idyll, pizza,
offer, ninja, khaki, kebab, rabbi, jiffy, offal, igloo, jazzy, affix}
My own start word (first guess word) is in the first 10 words, but it’ s not “saint” (which is actually quite a good start word). I got my own start word by testing all the words in the wordlist as start words and running the algorithm on the full wordlist: the word that got the best score is now used as start word . Note: These experiments was done using another program a long time ago (about 100 days ago).
wordle[“.....”, {“”, “”, “”, “”, “”}, “”, words]
{saint, crane, crone, coast, brine, crony, cause, briny, boast, slant, poise,
paint, shine, saner, prone, point, count, corny, clone,
<<2277>>,
occur, known, humus, hippo, fuzzy, dizzy, kappa, idyll, pizza,
offer, ninja, khaki, kebab, rabbi, jiffy, offal, igloo, jazzy, affix}
My own start word (first guess word) is in the first 10 words, but it’ s not “saint” (which is actually quite a good start word). I got my own start word by testing all the words in the wordlist as start words and running the algorithm on the full wordlist: the word that got the best score is now used as start word . Note: These experiments was done using another program a long time ago (about 100 days ago).
Proof of concept: total score so far
Proof of concept: total score so far
I've now used this approach for 109 days and got the following distribution of points :
1 : 0
2 : 11
3 : 38
4 : 42
5 : 12
6 : 5
missed : 0
This is an average of 3.6422 points. See more on that in the section “Calculate the average of the Wordle scores” below.
1 : 0
2 : 11
3 : 38
4 : 42
5 : 12
6 : 5
missed : 0
This is an average of 3.6422 points. See more on that in the section “Calculate the average of the Wordle scores” below.
The solver
The solver
Below is the code for the Wordle solve. It has a method for each of the three constraints:
* correct position (checkCorrectPos[])
* correct chars (checkCorrectChars[]))
* not in word (checkNotInWord[])
and for the scoring method mentioned above (sortCandidates[]).
Some of the methods can probably be done neater, especially checkCorrectChars[].
For sortCandidates[], see the section “Frequency tables for the scoring” above.
* correct position (checkCorrectPos[])
* correct chars (checkCorrectChars[]))
* not in word (checkNotInWord[])
and for the scoring method mentioned above (sortCandidates[]).
Some of the methods can probably be done neater, especially checkCorrectChars[].
For sortCandidates[], see the section “Frequency tables for the scoring” above.
In[]:=
(*helper:zippingtwolists*)zip[a_List,b_List]:=Transpose[{a,b}];(*Characterincorrectposition*)checkCorrectPos[chars_,word_]:=Module[{},StringMatchQ[word,RegularExpression@chars]](*Characterinwordbutinwrongposition*)(*helper*)checkChars1[word_,chars_]:=StringContainsQ[word,#]&/@StringSplit[chars,""];checkCorrectChars[chars_,word_]:=Module[{pos,zs,len=StringLength[word],t1},zs=zip[StringSplit[word,""],chars];(*allcharsareinword*)t1=And@@Flatten[(#==""||checkChars1[word,#])&/@chars,1];(*nocharsisincorrectposition*)If[t1,And@@(#[[2]]==""||Not[StringContainsQ[#[[1]],#[[2]]]]&/@zs)]](*Nocharacterincharsareintheword*)checkNotInWord[chars_,word_]:=Module[{},And@@(chars==""||Not[StringContainsQ[word,#]]&/@StringSplit[chars,""])](*Sortthecandidatewords.*)sortCandidates[candidates_]:=Module[{alphas,scores},(*Thisisthefrequencyorder(reversed)foreachpositionsoftheword.*)alphas={"xzyjkquinovhewlrmdgfaptbcs","zqfkgxvbsdymcwptnhulieroaj","qjhzkxfwyvcbpmgdstlnrueoia","qjxyzbwhfvpkmdguotcrilasne","qvjuzxbiwfcsgmpoakdnhlrtye"};SortBy[candidates,scoreWord[#,alphas]&]//Reverse](*Scoreaword*)scoreWord[word_,alphas_]:=Module[{score1,score2},(*Thescoreisthetotalofitspositionsinthealphasstringsabove+a100pointscreditifthewordhasuniquecharacters.*)score1=Total@(StringPosition[alphas[[#]],StringTake[word,{#}]][[1,1]]&/@Range[5]);score2=If[Length[DeleteDuplicates[StringSplit[word,""]]]==5,100,0];score1+score2](*Mainfunction:callalltheconstraintsandsortthecandidatewordsaccordingtothescoremethod*)wordle[correctPos_,correctChars_,notInWord_,words_]:=Module[{candidates},candidates=Select[words,checkCorrectPos[correctPos,#]&];candidates=Select[candidates,checkCorrectChars[correctChars,#]&];candidates=Select[candidates,checkNotInWord[notInWord,#]&];sortCandidates[candidates]]
Here are some examples, but let’s first load the word list. Here I’ve used a simple Wordle word list which contains all the 2315 Wordle target words.
In[]:=
wordlist="wordle_small.txt"words=StringSplit[Import[wordlist],"\n"];Length[words](*Allwords*)AbsoluteTiming[Short[wordle[".....",{"","","","",""},"",words],5]]wordle["i.e..",{"","","nr","n",""},"sla",words]//AbsoluteTimingwordle["...n.",{"","","","",""},"slat",words]//AbsoluteTimingwordle[".r.n.",{"","","","",""},"slatcoe",words]//AbsoluteTimingwordle[".....",{"s","a","a","s","e"},"lntcu",words]//AbsoluteTiming
Out[]=
wordle_small.txt
Out[]=
2315
Out[]=
{0.146758,{saint,crane,crone,coast,brine,crony,cause,briny,boast,slant,poise,paint,shine,saner,prone,point,count,corny,clone,2277,occur,known,humus,hippo,fuzzy,dizzy,kappa,idyll,pizza,offer,ninja,khaki,kebab,rabbi,jiffy,offal,igloo,jazzy,affix}}
Out[]=
{0.006679,{inert}}
Out[]=
{0.015284,{crone,brine,crony,briny,prone,corny,borne,prune,drone,phone,brink,phony,bound,pound,grind,frond,found,bring,drink,being,whine,fiend,chunk,whiny,prong,mound,horny,urine,round,drunk,irony,doing,wound,hound,opine,wring,rhino,downy,wrong,wrung,ebony,ovine,dying,young,owing,eying,vying,eking,penne,penny,bunny,funny,going,ninny,ozone,icing}}
Out[]=
{0.008328,{briny,brink,grind,bring,drink,drunk,wring,wrung}}
Out[]=
{0.106021,{askew,essay}}
Frequency tables for the scoring
Frequency tables for the scoring
The main feature of the scoring method is the (reversed) list of the characters sorted in order the most common characters for each position in the words (from some wordlist.)
In[]:=
createFreq[words_]:=Module[{t,map},map=Table[Association[],{i,1,5}];Do[Do[t=StringTake[w,{i}];map[[i]][[t]]=Lookup[map[[i]],t,0]+1,{i,1,5}],{w,words}];Reverse@Flatten[{Keys@Reverse[SortBy[#,2]],Complement[Alphabet[],Keys[#]]}]&/@map](*Shorterversion*)createFreq2[words_]:=Module[{k},Table[k=Keys[SortBy[Counts[StringTake[#,{pos}]&/@words],2]];StringJoin[Flatten[{Complement[Alphabet[],k],k}],""],{pos,1,5}]](*createFreq[words]*)createFreq2[words]//TableForm
Out[]//TableForm=
xzyjkquinovhewlrmdgfaptbcs |
jzqfkgxvbsdymcwptnhulieroa |
qjhzkxfwyvcbpmgdstlnrueoia |
qjxyzbwhfvpkmdguotcrilasne |
jqvuzxbiwfcsgmpoakdnhlrtye |
Calculate the average of the Wordle scores
Calculate the average of the Wordle scores
Here is a simple calculator of the average of one’s Wordle scores.
As mentioned above, after 108 rounds I’ve got an average of about 3.65.