The Problem

We are given 7 numbers that are linked to a number in the alphabet (1=A, 2=B…26=Z). We must then use those 7 numbers to find an english word that fits the clue. Each number, however, is linked to that letter in the alphabet, plus the next 4 letters. This gives us n+4 combinations for the number given. For example, if we are given the number 1, this could be A, B, C, D, E, or F. We then must find the cartesian product of all possible combinations of letters. We must use the 7 numbers given find a single 7 letter word that fits a clue.

For this example, let’s use the following puzzle parameters:

Clue Past tense noise of an animal
Numbers [16, 20, 1, 2, 9, 3, 4]

The cartesian product can be illustrated as follows (only for 2 sets of 3 possibilities): cartesian product

We will need to create a Python program that gives the cartesian products of (n+4)x7 possibilities. We will then run all combinations against a English dictionary to find any matches to english words.

Finding Python Libraries

First, we need to find a library that will create a list of all possible combinations of letters. After some research, I found the product() iterator in the itertools library.

The nested loops cycle like an odometer with the rightmost element advancing on every iteration. This pattern creates a lexicographic ordering so that if the input’s iterables are sorted, the product tuples are emitted in sorted order.

We also need to use the slice() tool from the more-itertools library. This will yield slices of length n from the sequence seq. For example:

>>> list(sliced((1, 2, 3, 4, 5, 6), 3))
[(1, 2, 3), (4, 5, 6)]

Notice how we have a list of 6 individual numbers that we then set the slice() to be slices of 3 in length.

Next, we will need to check all strings in the list, generated by the product() iterator, against an English dictionary. Pyenchant is a spellchecking library for Python, based on the excellent Enchant library. We can simply check all strings against the Pyenchant library, using d.check("word"), which will either give us a response of True or False.

Building the Program

First, we must import all of the libraries we will be using.

import itertools
from itertools import *
from more_itertools import *
import enchant
d = enchant.Dict("en_US")
import math

Here we import itertools, more_itertools, enchant, we swet the enchant dictionary to english (“en_US”), and math.

We will then ask for the seven numbers and store them in a list. We will also set limitations on what numbers can be given. 0-26 are the only numbers we will accept. The integers will then be linked to a dictionary of number-letter pairs.

letters = []
maxLength = 7
 
while len(letters) < maxLength:
    letter = int(input("Please input number: "))
    if letter < 0 or letter > 26:
        print("Invalid number!")
        break
    letters.append(letter)
    
print(letters)
 
letterOne = letters[0]
letterTwo = letters[1]
letterThree = letters[2]
letterFour = letters[3]
letterFive = letters[4]
letterSix = letters[5]
letterSeven = letters[6]
 
letterNumber = {
    0 : "/abcd",
    1 : "abcde", 
    2 : "bcdef", 
    3 : "cdefg",
    4 : "defgh",
    5 : "efghi",
    6 : "fghij",
    7 : "ghijk",
    8 : "hijkl",
    9 : "ijklm",
    10 : "jklmn",
    11 : "klmno",
    12 : "lmnop",
    13 : "mnopq",
    14 : "nopqr",
    15 : "opqrs",
    16 : "pqrst",
    17 : "qrstu",
    18 : "rstuv",
    19 : "stuvw",
    20 : "tuvwx",
    21 : "uvwxy",
    22 : "vwxyz",
    23 : "wxyzz",
    24 : "xyzzz",
    25 : "yzzzz",
    26 : "zzzzz"
}

Using the above code we link the first integer given to a string of letters. If we are looking at the numbers given at the top with the clue: [16, 20, 1, 2, 9, 3, 4], this would give us the following strings letterOne would be ["pqrst"], letterTwo would be ["tuvwx"]… and letterSeven would be ["defgh"].

Now we can overrite the letterOne-letterSeven variables with the string from the letterNumber dictionary, corresponding to that number. Next, we can split up the five letters in each string by breaking them up by location in the string (position 0-4).

letterOne = letterNumber[letterOne]
letterTwo = letterNumber[letterTwo]
letterThree = letterNumber[letterThree]
letterFour = letterNumber[letterFour]
letterFive = letterNumber[letterFive]
letterSix = letterNumber[letterSix]
letterSeven = letterNumber[letterSeven]
   
 
letterOne = letterOne[0], letterOne[1], letterOne[2], letterOne[3], letterOne[4]
letterTwo = letterTwo[0], letterTwo[1], letterTwo[2], letterTwo[3], letterTwo[4]
letterThree = letterThree[0], letterThree[1], letterThree[2], letterThree[3], letterThree[4]
letterFour = letterFour[0], letterFour[1], letterFour[2], letterFour[3], letterFour[4]
letterFive = letterFive[0], letterFive[1], letterFive[2], letterFive[3], letterFive[4]
letterSix = letterSix[0], letterSix[1], letterSix[2], letterSix[3], letterSix[4]
letterSeven = letterSeven[0], letterSeven[1], letterSeven[2], letterSeven[3], letterSeven[4]

Now we can start using the itertools product() iterator to find the cartesian product of letterOne to letterSeven. We will then use the more-itertools sliced() to seperate the extremely long return (should be over 78,000 iterations) of letters into sets of 7.

wordList = []
counter = 0
result = itertools.product(letterOne, letterTwo, letterThree, letterFour, letterFive, letterSix, letterSeven)
for each in result:
    wordList += list(each)
    counter +=1
 
wordList = (list(sliced(wordList, 7)))

For troubleshooting, we could use the following code to print out ALL 78,000+ possibilities (simply click at any point to freeze the strings displayed):

print('My list:', *wordList, sep='\n')

Finally, we can check each of the strings against the enchant english dictionary.

res = [''.join(ele) for ele in wordList] 
 
english_words = []
for word in res:
    if d.check(word):
        english_words.append(word)

english_wordsLength = len(english_words)

print(f"{english_wordsLength} possible answer(s):")

for l in english_words:
    print(l[0:])

This will check all strings against the enchant english dictionary and append any words that have a return of True to the english_words list. We then print the number of possible answers and each individual string!

If we used the numbers given with the clue at the beginning of this post, we should have the following output:

[16, 20, 1, 2, 9, 3, 4]
Number of iterations: 78125
7 possible answer(s):
puddled
quacked
ruddled
stabled
stacked
steeled
swacked

Which one is the past tense of an animal sound?

Conclusion

I learned quite a bit from this little program, mainly how important it is to find helpful Python packages! Utilizing both StackOverflow and PyPi to find helpful tools/code is extremely helpful.

The following is the complete code for the puzzle solver:

import itertools
from itertools import *
from more_itertools import *
import enchant
d = enchant.Dict("en_US")
import math
 
letters = [15, 5, 15, 15, 5, 15, 10]
maxLength = 7
 
while len(letters) < maxLength:
    letter = int(input("Please input number: "))
    if letter < 0 or letter > 26:
        print("Invalid number!")
        break
    letters.append(letter)
    
print(letters)
 
letterOne = letters[0]
letterTwo = letters[1]
letterThree = letters[2]
letterFour = letters[3]
letterFive = letters[4]
letterSix = letters[5]
letterSeven = letters[6]
 
letterNumber = {
    0 : "/abcd",
    1 : "abcde", 
    2 : "bcdef", 
    3 : "cdefg",
    4 : "defgh",
    5 : "efghi",
    6 : "fghij",
    7 : "ghijk",
    8 : "hijkl",
    9 : "ijklm",
    10 : "jklmn",
    11 : "klmno",
    12 : "lmnop",
    13 : "mnopq",
    14 : "nopqr",
    15 : "opqrs",
    16 : "pqrst",
    17 : "qrstu",
    18 : "rstuv",
    19 : "stuvw",
    20 : "tuvwx",
    21 : "uvwxy",
    22 : "vwxyz",
    23 : "wxyzz",
    24 : "xyzzz",
    25 : "yzzzz",
    26 : "zzzzz"
}
 
letterOne = letterNumber[letterOne]
letterTwo = letterNumber[letterTwo]
letterThree = letterNumber[letterThree]
letterFour = letterNumber[letterFour]
letterFive = letterNumber[letterFive]
letterSix = letterNumber[letterSix]
letterSeven = letterNumber[letterSeven]
   
 
letterOne = letterOne[0], letterOne[1], letterOne[2], letterOne[3], letterOne[4]
letterTwo = letterTwo[0], letterTwo[1], letterTwo[2], letterTwo[3], letterTwo[4]
letterThree = letterThree[0], letterThree[1], letterThree[2], letterThree[3], letterThree[4]
letterFour = letterFour[0], letterFour[1], letterFour[2], letterFour[3], letterFour[4]
letterFive = letterFive[0], letterFive[1], letterFive[2], letterFive[3], letterFive[4]
letterSix = letterSix[0], letterSix[1], letterSix[2], letterSix[3], letterSix[4]
letterSeven = letterSeven[0], letterSeven[1], letterSeven[2], letterSeven[3], letterSeven[4]
 
wordList = []
counter = 0
result = itertools.product(letterOne, letterTwo, letterThree, letterFour, letterFive, letterSix, letterSeven)
for each in result:
    wordList += list(each)
    counter +=1
 
wordList = (list(sliced(wordList, 7)))
 
#print('My list:', *wordList, sep='\n') #only needed for troubleshooting
 
print(f"Number of iterations: {counter:n}")
 
res = [''.join(ele) for ele in wordList] 
 
english_words = []
for word in res:
    if d.check(word):
        english_words.append(word)

english_wordsLength = len(english_words)

print(f"{english_wordsLength} possible answer(s):")

for l in english_words:
    print(l[0:])