a4_2.py

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()