I don't like this dictionary thing. W6
I’m struggling to keep the shape of the solutions dictionary in my mind. Therefore I need something simpler.
The solution dictionary is built like this:
class SolutionDictionary:
def __init__(self, guesses, solutions):
self.dict = self.create_dict(guesses, solutions)
@staticmethod
def create_dict(guesses, solutions):
solutions_dict = {} # guess -> dict (score -> [solutions])
for guess in guesses:
guess_dict = {} # score -> [solutions]
solutions_dict[guess] = guess_dict
for solution in solutions:
score = guess.score(solution)
if not score in guess_dict:
guess_dict[score] = ScoredWords(score)
guess_dict[score].add_word(solution)
return solutions_dict
This is a dictionary keyed by guess, pointing to a dictionary keyed by score, such that if score
= guess.score(solution)
, solutions_dict[guess][score] is an instance of ScoredWords containing that score, and all the solutions that result in that score.
I find that impossible to understand, impossible to describe in words. This is not a good sign. I need a better idea. Let me try new words.
For every possible guess, we want a GuessDescription, consisting of a ScoreDescription, consisting of a score, and a list of all the solutions having that score. Let’s rename ScoredWords to ScoreDescription.
Now we have this:
def create_dict(guesses, solutions):
solutions_dict = {} # guess -> dict (score -> [solutions])
for guess in guesses:
guess_dict = {} # score -> [solutions]
solutions_dict[guess] = guess_dict
for solution in solutions:
score = guess.score(solution)
if not score in guess_dict:
guess_dict[score] = ScoreDescription(score)
guess_dict[score].add_word(solution)
return solutions_dict
Now let’s make a GuessDescription by intention:
@staticmethod
def create_dict(guesses, solutions):
solutions_dict = {} # guess -> dict (score -> [solutions])
for guess in guesses:
guess_desc = GuessDescription()
solutions_dict[guess] = guess_desc
for solution in solutions:
score = guess.score(solution)
guess_desc.add_word(score, solution)
return solutions_dict
That demands this:
class GuessDescription:
def __init__(self):
self.score_descriptions = {}
def add_word(self, score, solution):
try:
description = self.score_descriptions[score]
except KeyError:
description = ScoreDescription(score)
description.add_word(solution)
That’ll break some tests, telling us that our refactoring is not complete. The first error is:
def solutions_for(self, guess, score):
try:
> return self.dict[guess][score]
E TypeError: 'GuessDescription' object is not subscriptable
Yes. I think we can do this to get to the next flaw:
class SolutionDictionary:
def solutions_for(self, guess, score):
guess_description = self.dict[guess]
return guess_description.solutions_for(score)
class GuessDescription:
def solutions_for(self, score):
try:
return self.score_descriptions[score]
except KeyError:
return ScoreDescription(score)
Continues to break, now saying:
Expected :<score_description.ScoreDescription object at 0x105a8c090>
Actual :<score_description.ScoreDescription object at 0x105a8c310>
Looks like we need ScoreDescriptions to understand __eq__
. But we have that. What’s up?
Ah. This was wrong:
class GuessDescription:
def add_word(self, score, solution):
try:
description = self.score_descriptions[score]
except KeyError:
description = ScoreDescription(score)
self.score_descriptions[score] = description # added
description.add_word(solution)
I wasn’t adding the new ScoreDescription to the dictionary.
One more test failing. Create statistics needs revision. We have:
def create_statistics(self):
stats = []
for word in self.dict:
word_dict = self.dict[word] # {score -> scoredWords}
number_of_buckets = len(word_dict)
max_words = max(len(bucket) for bucket in word_dict.values())
min_words = min(len(bucket) for bucket in word_dict.values())
avg_words = sum(len(bucket) for bucket in word_dict.values()) / number_of_buckets
stat = Statistic(word, number_of_buckets, max_words, min_words, avg_words)
stats.append(stat)
def my_key(stat: Statistic):
return -stat.number_of_buckets
stats.sort(key=my_key)
return stats
We need:
def create_statistics(self):
stats = []
for word in self.dict:
guess_description = self.dict[word] # {score -> scoredWords}
number_of_buckets = guess_description.number_of_buckets
max_words = max(len(bucket) for bucket in guess_description.buckets)
min_words = min(len(bucket) for bucket in guess_description.buckets)
avg_words = sum(len(bucket) for bucket in guess_description.buckets) / number_of_buckets
stat = Statistic(word, number_of_buckets, max_words, min_words, avg_words)
stats.append(stat)
def my_key(stat: Statistic):
return -stat.number_of_buckets
stats.sort(key=my_key)
return stats
class GuessDescription:
def __init__(self):
self.score_descriptions = {}
def add_word(self, score, solution):
try:
description = self.score_descriptions[score]
except KeyError:
description = ScoreDescription(score)
self.score_descriptions[score] = description
description.add_word(solution)
@property
def buckets(self):
return self.score_descriptions.values()
@property
def number_of_buckets(self):
return len(self.score_descriptions)
def solutions_for(self, score):
try:
return self.score_descriptions[score]
except KeyError:
return ScoreDescription(score)
We added the buckets
and number_of_buckets
properties. Tests are green. I am fried. Commit: refactor to provide GuessDescription and ScoreDescription objects.
Summary
I think these two objects make things easier to understand. I’ll try to explain them next time. Right now … well, I’ll try:
The SolutionDictionaryis a keyed collection (dictionary) of guess word to instances of GuessDescription.
A GuessDescription is a keyed collection of score to instances of ScoreDescription.
ScoreDescription is an object holding a score, and a WordCollection holding all the solutions such that the guess gets that score from each of those solutions.
It’s still weird, isn’t it?
Try this:
SolutionDictionary is an object that knows about guesses, solutions, and scores. It is created from two WordCollections, a collection of guesses and a collection of possible solutions.
Given a SolutionDictionary sd
, sd.solutions_for(guess_word, score)
returns all the solutions that would give the guess_word the provided score. That is, it returns all the solutions that might be correct given guess_word and its score from Wordle.
Enough. See you next time!