Refactor constraints into it's own file.

This commit is contained in:
dongar committed 2023-05-23 12:29:31 -05:00
1 parent 7cf685b507
commit 36c26dcf9f
4 files changed
+153 -150

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/python
from collections import defaultdict
import dictionary
from wordle import LetterResult
class Constraints:
def __init__(self):
# Array of known letters in result with None for unknown.
self.correct = [None] * dictionary.WORDLE_LENGTH
# Array with per-letter sets of known bad letters.
self.incorrect = [set() for _ in range(dictionary.WORDLE_LENGTH)]
# Dict of letter -> int with minimum required count of letter in word.
self.count = defaultdict(int)
# If letter is present in set, value in min is both min and max.
self.exact = set()
# List of LetterResult values.
def update_graded(self, guess, grade):
guess = guess.lower()
new_count = defaultdict(int)
for i, letter in enumerate(guess):
match grade[i]:
case LetterResult.CORRECT:
self.correct[i] = letter
new_count[letter] += 1
case LetterResult.MOVE:
self.incorrect[i].add(letter)
new_count[letter] += 1
case LetterResult.INCORRECT:
self.incorrect[i].add(letter)
self.exact.add(letter)
new_count[letter] += 0 # Ensure letter is in dict
for letter, c in new_count.items():
self.count[letter] = max(c, self.count[letter])
def matches(self, guess):
counts = defaultdict(int)
# All known correct letters in place.
for i, letter in enumerate(guess):
# If we have a known letter, and don't match...
if self.correct[i] and self.correct[i] != letter:
return False
# If we have a letter that's known to be incorrect in the current location...
if letter in self.incorrect[i]:
return False
counts[letter] += 1
for letter, c in self.count.items():
if letter in self.exact:
if counts[letter] != c:
return False
else:
if counts[letter] < c:
return False
return True
def filter_words(self, words=dictionary.words()):
return set(filter(lambda w: self.matches(w), words))
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/python
import unittest
import constraints
from wordle import LetterResult
class TestConstraints(unittest.TestCase):
def test_empty(self):
consts = constraints.Constraints()
self.assertTrue(consts.matches("abcde"))
self.assertTrue(consts.matches("bcdef"))
def test_correct(self):
consts = constraints.Constraints()
consts.update_graded(
"abcde",
[
LetterResult.CORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(consts.matches("amnop"))
self.assertTrue(consts.matches("aaaaa"))
self.assertFalse(consts.matches("abcde"))
self.assertFalse(consts.matches("lmnop"))
self.assertFalse(consts.matches("aedcb"))
self.assertFalse(consts.matches("amnob"))
def test_correct_count_limit(self):
consts = constraints.Constraints()
consts.update_graded(
"aazzz",
[
LetterResult.CORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(consts.matches("abcde"))
self.assertTrue(consts.matches("abbbb"))
self.assertFalse(consts.matches("aabbb"))
self.assertFalse(consts.matches("abbba"))
self.assertFalse(consts.matches("bbbbb"))
self.assertFalse(consts.matches("bbbba"))
self.assertFalse(consts.matches("babbb"))
def test_move(self):
consts = constraints.Constraints()
consts.update_graded(
"abcde",
[
LetterResult.MOVE,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(consts.matches("zzzza"))
self.assertTrue(consts.matches("zzzaa"))
self.assertTrue(consts.matches("zaaaa"))
self.assertFalse(consts.matches("azzzz"))
self.assertFalse(consts.matches("zzzzz"))
self.assertFalse(consts.matches("zzzba"))
if __name__ == "__main__":
unittest.main()
-83
View File
@@ -1,83 +0,0 @@
#!/usr/bin/python
import wordle
from wordle import LetterResult
import unittest
class TestConstraints(unittest.TestCase):
def test_empty(self):
constraints = wordle.Constraints()
self.assertTrue(constraints.matches("abcde"))
self.assertTrue(constraints.matches("bcdef"))
def test_correct(self):
constraints = wordle.Constraints()
constraints.update_graded(
"abcde",
[
LetterResult.CORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(constraints.matches("amnop"))
self.assertTrue(constraints.matches("aaaaa"))
self.assertFalse(constraints.matches("abcde"))
self.assertFalse(constraints.matches("lmnop"))
self.assertFalse(constraints.matches("aedcb"))
self.assertFalse(constraints.matches("amnob"))
def test_correct_count_limit(self):
constraints = wordle.Constraints()
constraints.update_graded(
"aazzz",
[
LetterResult.CORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(constraints.matches("abcde"))
self.assertTrue(constraints.matches("abbbb"))
self.assertFalse(constraints.matches("aabbb"))
self.assertFalse(constraints.matches("abbba"))
self.assertFalse(constraints.matches("bbbbb"))
self.assertFalse(constraints.matches("bbbba"))
self.assertFalse(constraints.matches("babbb"))
def test_move(self):
constraints = wordle.Constraints()
constraints.update_graded(
"abcde",
[
LetterResult.MOVE,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
LetterResult.INCORRECT,
],
)
self.assertTrue(constraints.matches("zzzza"))
self.assertTrue(constraints.matches("zzzaa"))
self.assertTrue(constraints.matches("zaaaa"))
self.assertFalse(constraints.matches("azzzz"))
self.assertFalse(constraints.matches("zzzzz"))
self.assertFalse(constraints.matches("zzzba"))
if __name__ == "__main__":
unittest.main()
+4 -67
View File
@@ -2,11 +2,11 @@
import random import random
import string import string
from enum import Enum from enum import Enum
from collections import defaultdict from collections import defaultdict
import dictionary import dictionary
import constraints
class LetterResult(Enum): class LetterResult(Enum):
@@ -137,72 +137,9 @@ def scored_words(wordle_dictionary=dictionary.words()):
return [(word, scored_words[word]) for word in result] return [(word, scored_words[word]) for word in result]
class Constraints:
def __init__(self):
# Array of known letters in result with None for unknown.
self.correct = [None] * dictionary.WORDLE_LENGTH
# Array with per-letter sets of known bad letters.
self.incorrect = [set() for _ in range(dictionary.WORDLE_LENGTH)]
# Dict of letter -> int with minimum required count of letter in word.
self.count = defaultdict(int)
# If letter is present in set, value in min is both min and max.
self.exact = set()
# List of LetterResult values.
def update_graded(self, guess, grade):
guess = guess.lower()
new_count = defaultdict(int)
for i, letter in enumerate(guess):
match grade[i]:
case LetterResult.CORRECT:
self.correct[i] = letter
new_count[letter] += 1
case LetterResult.MOVE:
self.incorrect[i].add(letter)
new_count[letter] += 1
case LetterResult.INCORRECT:
self.incorrect[i].add(letter)
self.exact.add(letter)
new_count[letter] += 0 # Ensure letter is in dict
for letter, c in new_count.items():
self.count[letter] = max(c, self.count[letter])
def matches(self, guess):
counts = defaultdict(int)
# All known correct letters in place.
for i, letter in enumerate(guess):
# If we have a known letter, and don't match...
if self.correct[i] and self.correct[i] != letter:
return False
# If we have a letter that's known to be incorrect in the current location...
if letter in self.incorrect[i]:
return False
counts[letter] += 1
for letter, c in self.count.items():
if letter in self.exact:
if counts[letter] != c:
return False
else:
if counts[letter] < c:
return False
return True
def filter_dictionary(constraints, wordle_dictionary=dictionary.words()):
return set(filter(lambda w: constraints.matches(w), wordle_dictionary))
if __name__ == "__main__": if __name__ == "__main__":
constraints = Constraints() consts = constraints.Constraints()
constraints.update_graded( consts.update_graded(
"cares", "cares",
[ [
LetterResult.INCORRECT, LetterResult.INCORRECT,
@@ -213,6 +150,6 @@ if __name__ == "__main__":
], ],
) )
valid_words = filter_dictionary(constraints) valid_words = consts.filter_words(dictionary.words())
print("Top: ", scored_words(valid_words)[:5]) print("Top: ", scored_words(valid_words)[:5])