UA-5095748-1

Friday, December 6, 2024

What would be a good Python demo for non-technical students? (part 2)

See part one to build a word game solver.

Did you catch the flaw? Here is one more hint. See the bold section

list(filter(lambda x: len(x)>=3 and len(x) <= len(findchar) and all(c in findchar for c in x) and len(set(tuple(x))) == len(x), words))

The code assumes all the given letters are unique, and that is a problem! Here is a screenshot of a higher level game with 2 Rs as given letters. The code would not find "narrow" as a solution in this example. 

There is nothing wrong with the original. code, per se. The code is working exactly as intended, but our assumption and formulation of the rules was incorrect. This is another example why it’s hard to write good software. I don’t know when AI will be able to catch these subtle assumption errors. 

Crux of the issue is that our original definition of the rules were flawed. A better rules would be 

  • A word must be 3 or more letters
  • A word must be shorter or same length of all the given letters
  • A word can only be a permutation of the given letters

Let’s build a better word game solver

  1. First few steps are the same as before. 
    • rawwords = open("wordlist.txt").readlines()
    • words = [word.strip() for word in rawwords if len(word.strip()) > 0]
    • findchar = list("rwornia")
  2. Python has a library to generate permutations
    • from itertools import permutations
  3. Make a function to pull different length of permutations
    • def makeperm(n): 
    •   return ["".join(p) for p in permutations(findchar,n)]
  4. Build a complete list of permutations from 3 and higher
    • p = []
    • for n in range(3,len(findchar)+1) : 
    •   p += makeperm(n)
  5. Apply the rules
    • list(filter(lambda x: len(x)>=3 and len(x) <= len(findchar) and x in p, words))
  6. The solution is now much slower, but correctly have words like “narrow”
    • ['air', 'ani', 'arno', 'arrow', 'awn', 'ion', 'iowa', 'iowan', 'ira', 'iran', 'iron', 'iwo', 'narrow', 'noir', 'nor', 'noria', 'now', 'oar', 'ora', 'oran', 'own', 'owr', 'rain', 'rani', 'rao', 'rari', 'raro', 'raw', 'roan', 'roar', 'roi', 'ron', 'row', 'rowan', 'wai', 'wain', 'wan', 'war', 'warn', 'win', 'won', 'worn']

Here is the code in its entirety 

rawwords = open("wordlist.txt").readlines()
words = [word.strip() for word in rawwords if len(word.strip()) > 0]
findchar = list("rwornia")
from itertools import permutations
def makeperm(n): 
  return ["".join(p) for p in permutations(findchar,n)]
p = []
for n in range(3,len(findchar)+1) : 
  p += makeperm(n)
list(filter(lambda x: len(x)>=3 and len(x) <= len(findchar) and x in p, words))

Readers would also note some issues with this code

  • Referencing a global name findchar in a function is bad coding practice! I got lazy. :) 
  • Did NOT need to filter for len(x)>=3 and len(x) <= len(findchar) again. The current example has 13,650 possible permutation to search for each of the approximately 70,000 words. We made the code faster by adding duplicative but low cost checks first, thereby eliminating much of the expensive computations. 

How can this solution be improved? 

No comments: