def check_all_words():
# Read the dictionary of all words
f = open("words.txt")
dictionary = set()
for line in f:
dictionary.add(line.strip())
f.close()
# Add some missing words in dictionary
dictionary.add("a")
dictionary.add("i")
dictionary.add("")
# Initialize variables tracking the longest reducible word(s)
# that we have seen.
bestWords = []
bestLength = 0
# Initialize cache of all reducible words we have seen.
# The cache (memo) enables us to check each word for
# reducibility at most once. This makes the program
# run faster (fewer checks) at the expense of using more
# memory (words stored in cache).
memo = {}
for word in dictionary:
# For more efficiency, we should check the words
# sorted by length, so that the first reducible
# word is also the longest.
# If the word is shorter than our best, we do not
# even bother to check it since it cannot be
# the word we are looking for.
if len(word) < bestLength:
continue
# If this word is reducible, it must be at least
# as long as our current best. If it is the same
# length, we add it to the "bestWords" list. If
# it is longer, we clear out "bestWords" and start
# over, updating "bestLength" at the same time.
if reducible(word, dictionary, memo):
if len(word) > bestLength:
#print("new best word", word)
bestLength = len(word)
bestWords = [ word ]
elif len(word) == bestLength:
#print("equal best word", word)
bestWords.append(word)
# After looking at all words in the dictionary, print out
# the results we saved.
print("Longest reducible word has", bestLength, "characters")
print("They are:")
for word in bestWords:
print_trail(word, memo)
print()
def reducible(word, dictionary, memo):
"""Return whether the given "word" is reducible."""
# Recursion termination condition: either all the letters
# have been removed, or we have already seen this word and
# know that it is reducible.
if len(word) == 0 or word in memo:
return True
# Loop through all possible subwords and check whether any
# of them is reducible. Because we only need to find one
# reducible subword, we can return immediately when we do
# find one.
# "memo" is the cache where we store the set of reducible
# words that we have seen. When we know that "word" is
# reducible, we add it into the cache. Note that we do not
# need to do so for subwords since it will have been added
# already by the calls to "reducible" with the subwords.
for subword in shorten(word):
if subword not in dictionary:
continue
if reducible(subword, dictionary, memo):
memo[word] = True
return True
return False
def shorten(word):
"""Return the set of strings one character shorter than "word"."""
return set([ word[:i] + word[i + 1:] for i in range(len(word)) ])
# Above statement is the "list comprehension" version of:
#
# words = []
# for i in range(len(word)):
# words.append(word[:i] + word[i + 1:])
# return set(words)
#
# Of course, we can directly create the set:
#
# words = set()
# for i in range(len(word)):
# words.add(word[:i] + word[i + 1:])
# return words
def print_trail(word, memo):
"""Print trail of subwords that make "word" reducible."""
# Note that "print_trail" is also recursive.
# We use the memo cache that we built to find a reducible
# subword and print its trail.
# Recursion termination condition
if len(word) == 0:
return
print(word, end=' ')
for subword in shorten(word):
if subword in memo:
# Recurse for good subword
print_trail(subword, memo)
# We only need one trail, so we can immediately
# return after printing one.
return
check_all_words()