def trie_test(self):
        items = ["potato", "pots", "buckle"]

        trie = Trie()
        for item in items:
            trie.add(item)
        trie_items = [x for x in trie.next()]
        self.assertEqual(items, trie_items)     # test that the trie contains the same items that we put in

        node = trie.find("pot")
        containing_words = [x for x in node.terminals("pot")]
        self.assertEqual(len(containing_words), 2)      # test find returns potato and pots, and NOT buckle
        self.assertIn("potato", containing_words)       # order does not matter
        self.assertIn("pots", containing_words)
        self.assertNotIn("buckle", containing_words)
class Vocabulary:
    def __init__(self):
        self.lexicon = Trie()

    def next(self):
        return self.lexicon.next()

    def __contains__(self, item):
        return self.lexicon.find(item) is not None

    def fetch(self, path):
        """Retrieves a set of words from the given file path. Function assumes each line in file is a word.
        :param path: The path to the vocabulary file.
        """
        with open(path, 'r') as document:
            for line in document:
                for word in line.split():
                    self.lexicon.add(word)

    def word_ladder(self, origin, destination):
        """Constructs a word ladder between the given words using the fetched vocabulary. A word ladder is a sequence of
        words, from origin to destination, where each intermediary word changes exactly one letter in the previous word.
        All intermediate words in the ladder must be real words.
        Constructing a word ladder loosely follows the methodology of A* path finding. A tree data structure is used to
        store a collection of words and the paths between them. The tree is filled first with the destination word and
        is then traversed breadth first adding each word's legal one character substitutions. Traversal ends when any
        path has reached the origin and that path's ancestry is returned.
        The tree is traversed breadth first so that the shortest path is found in all cases. A tree begins at the
        destination and works backwards to the origin so that the chosen path's ancestry is in the correct order.
        :param origin: The starting word to construct a word ladder from.
        :param destination: The word that the ladder traverses to.
        :return: A sequence of words that constitutes a word ladder.
        """
        paths = Tree()                        # tree stores all possible paths
        paths.add_root(destination)           # start at destination so that ancestry path is in the correct order
        visited = set()                       # no need for ANY branch to revisit a word that another branch has been to

        for node in paths.breadth_first():
            if node.data == origin:                     # if node is origin, the word ladder is complete
                path = []
                for ancestor in node.ancestor_data():   # construct a path from this nodes ancestors.
                    path.append(ancestor)
                return path
            else:
                for word in self.similar(node.data):        # add each similar word to this nodes path...
                    if word not in visited:                 # ...only if it hasn't been visited by ANY other tree path
                        node.add(word)
                        visited.add(word)
        return []                                           # no path was found

    def similar(self, word):
        """Searches for words similar to the given word by preforming character substitutions on each character in the
        given word.
        :param word: A word to find similar words to.
        """
        if word not in self:
            raise StopIteration
        for switch_position in range(len(word)):
            walker = self.lexicon.find(word[:switch_position])
            # Each child is a possible character substitution. A valid child is one that contains the remaining
            # original characters from the given word
            for key, child in walker.children.items():
                # Don't include the original character as a valid choice
                if word[switch_position] is not key:
                    # if on the last letter of 'word' add all children
                    if switch_position is len(word) - 1:
                        yield word[:switch_position] + key
                    # otherwise, check that each child contains remaining original characters from word
                    elif word[switch_position + 1:] in child:
                        yield word[:switch_position] + key + word[switch_position + 1:]