Wordle 3: analysing the effectiveness of the solver
Set the browser to Full Screen view, to give this worksheet and your code as much space as possible.
to continue. (After that any entries made into the worksheet will be automatically saved, and you can re-load the partially-completed worksheet in future – at which point you will be asked to auto-save it again).
Following-on from 'Wordle 2: writing an automated solver', in this worksheet your challenge
is to write an analyser that can evaluate:
Just how effective that automated solver is at solving puzzles,
by getting it attempt to solve all 2,315 of the valid target words, and measuring
the percentage that it solves within six attempts. For those successful cases,
it should also calculate the average number of attempts required.
Whether ARISE is the best starting word for that algorithm or, if not,
what is the best starting word.
and note the following:
Underneath all the imports you will see all the code you have written
in the previous projects, plus some new code that you will be using and/or modifying in this worksheet.
The new code currently has no main routine - but you will be adding one.
There are two ghosted tests at the bottom, each covering several test cases
and referring to functions that currently have only 'stub' implementations.
Total hints used: /
Step 2: implement 'attemptsNeededToSolve'
Unghost the test named attemptsNeededToSolve. This will
cause the tests to run, but they will all fail because
the function attemptsNeededToSolve immediately above the
test is just a 'stub' - the minimum code needed to compile.
A proper implementation for attemptsNeededToSolve will
re-use quite a bit of the logic from playReverseGame
but with these differences:
it is a function: it will not
not result in anything being displayed, so there is no need for the
callputAttemptIntoGrid, nor to create
grid in the first place.
it does not need to, and indeed cannot, ask the user for input - so
it should not have the callenterMark instruction.
Instead, we can get the mark by evaluating the markAttempt
function that we wrote in Wordle 1. However, we don't then need to use two instructions
to obtain the mark - we can write it as a single let instruction. (It could
still be a variable, but we have no need to re-assign it because it
defines mark anew each time around the while loop, anyway.
The while loop need test only for whether the puzzle is solved, not
for the number of attempts, because we want to know how many attempts it takes even if that is 7 or more - though any such case
will eventually be counted as a failure to solve the puzzle.
Because we will not be updating the display grid (where rows and columns start from zero),
attemptNo should be initialised to 1 rather than 0,
and it should be incremented by 1 whenever the mark for an attempt
is not all-greens.
Apart from those constraints, the rest of the logic required for attemptsNeededToSolve exists in
playReverseGame and you will be able to copy several whole instructions from one to the other.
Have a go at this now - using available hints only if you need them.
Hint 2-1: implementation in outline form
Hint 2-2: complete implementation
When your implementation of attemptsNeededToSolve
is complete, all the test-cases (asserts) in testattemptsNeededToSolve should pass.
Total hints used: /
Step 3: write a main to use the new function
Write a main routine that:
asks the user to enter a target word,
evaluates the attemptsNeededToSolve function for this target
using ARISE as the first attempt,
prints the result in the form (example only): 'Target word QUITE took 4 attempts to solve'
Hint 3-1: outline instructions
Hint 3-2: the input and output
Hint 3-3: complete main routine
When you have this implemented, run the program to find out how many attempts it takes
to identify the target words CHURN and then FIZZY.
Record your results here:
Total hints used: /
Step 4: analyse the algorithm for all potential target words
In order to identify the effectiveness of a Wordle solver we need to test it against all 2,315
valid target words, and from this determine the percentage of of the puzzles it was
able to solve in 6 or fewer attempts, and, for those successes, the average number of attempts needed.
Find the ghosted test named analyseAlgorithm and unghost it -
which will cause the test to run and the asserts to fail. Then look
at the stub implementation of the analyseAlgorithm just above the test.
Note that the return type for this function is (Float, Float) which
means that it returns a '2-tuple'- a tuple containing two values - which both happen to be Float
in this case.
A tuple is just a very simple, small, data structure for passing around more than one piece of data -
of the same or different types - in one go.
As it stands, the function always returns a pair of zeros. Take note of the syntax
for constructing the 2-tuple to be returned: tuple(something, something).
In the analyseAlgorithm function, define two variables:
success, representing the number of puzzles solved within 6 attempts. For now, initialise it to an example value of 7.
weightedSum, representing the sum of: (puzzles solved in one attempt x 1) + (puzzles solved in 2 attempts x 2) + and so on.
For now, initialise it to an example value of 8.
(The choice of those two temporary initial values will become apparent shortly).
These are both variables because later on we will need to replace the initial value with a calculated one in each case.
Below this, define two more values - successPercent and average - but for each these use a let instruction (because we won't need to re-assign them):
For successPercent give it a calculated value, derived from success such that it will represent a percentage of the number of validTargetsrounded to one decimal place..
The average is the weightedSum divided by successrounded to two decimal places.
Change the return instruction to return a tuple constructed from the values successPercent and average in that order.
If you have coded this correctly, then the first assert in the test should pass - but this is an artifical result, down entirely to those
numbers used to initialise the variables! So once you have
established that your implementation so far is correct, change the initial values of success and weightedSum to zero
(causing the assert to fail again.)
Hint 4-1: rounding
Hint 4-2: the two expressions in full
Hint 4-3: the complete function so far
Total hints used: /
Step 5: Calculate the number of successful solutions
Now we will calculate success the total number of puzzles that the algorithm solves within 6 attempts.
Add a loop that evaluates the number of attempts to solve each of the targetwords provided in validTargets,
and if that number is within the limit of six, increment the value of success by one.
Hint 5-1: the new code in outline
Hint 5-2: the complete function so far
Although none of the asserts in the new test will be passing yet, what do you notice
about the two values that are being generated by the function in the test?
Total hints used: /
Step 6: Calculate the weighted-sum of the number of attempts
Now we will calculate the weightedSum of the number of successful attempts, from which the average is subsequently derived.
Just before the loop, define a variable outcomes as an array of integers with seven members, initialised to zero.
(Strictly speaking we could manage with six members, since we are interested only in the outcomes of 1 to 6 attempts
- so the first element (number 0>) is wasted.
However, that's a small price to pay for simpler code - as you will see.
Within the loop, whenever a target has been identified within six attempts,
increment by one the value in the array corresponding to the number of attempts taken.
Immediately after the end of the current loop, add a new for loop that goes through the index values from 1 to 6
(we can just ignore the unused element zero), then adds into weightedSum the value of that element times the index number.
Make sure you understand why this will produce the 'weighted sum' of the results.
Hint 6-1: the array definition
Hint 6-2: how to update the array
Hint 6-3: the new loop in outline
Hint 6-4: the completed instruction for updating 'outcomes'
Hint 6-5: the completed function
When implemented, all the asserts in the test analyseAlgorithm should be passing.
Total hints used: /
Step 7: Re-write main to present the results of the analysis
Your task is now to re-write main (start by deleting all the existing instructions within it) to understake the analysis and present
the results.
Because we don't know how long this analysis may take, at the beginning of main print the message 'Starting...' onto the display.
The constantallValidTargetWords is a single string containing 2,315 words separated by spaces. However, the
function analyseAlgorithm expects a list of words, where each member of the list is a single word, with no spaces.
If you are not familiar with how to do this, look up the various dotted-methods available on a Stringshow in Help.
Evaluate the function analyseAlgorithm, passing in the "ARISE" as the chosen first attempt, and the list of inputStringWithLimits
you have just created from allValidTargetWords. Because this will pass back two values as a tuple, look up how to 'deconstruct' a tuple (show in Help)
into two values named successPercent and average. (You can do this in a let instruction.)
Print these values formatted in the format: 'xx.x% of the puzzles solved within 6 attempts, with an average of x.xx attempts'
Hint 7-1: expression to split the target words into a list
Hint 7-2: deconstructing the tuple returned by analyseAlgorithm
Hint 7-3: how to create the formatted output
Hint 7-4: the completed 'main'
Run the program
Record the results (% solved and the average number of attempts) here:
Finally, try changing ARISE to an alternative first attempt word. Maybe try a few likely candidates.
Record the result from the best first attempt word that you found, whether or not it was better than ARISE.
Total hints used: /
Congratulations! Worksheet completed
Hopefully you were impressed at the effectiveness of the algorithm that you wrote. It would likely beat many human players - over a reasonable run of puzzles!
If you have time, there are plenty of ways that you could extend this investigation into Wordle, including:
Add a loop around the last three instructions in main to evaluate the effectiveness of using each of the valid target words
as the first attempt, and keeping a running track of the best so far. Warning: this could take several hours to complete! Even more
ambitious (and taking more than seven times longer) would be to try it for each of the 15,000+ validAttempt words as the first attempt.
Read up online about more sophisticated algorithms - several of which are guaranteed to solve 100% of puzzles in five
or fewer attempts, and with an average of 3.5 or lower. And someone has calculated the optimum strategy but to replicate that work, you're gonna need a bigger computer!