--- /dev/null
+#!/usr/bin/env python3
+
+from typing import Any, Sequence
+
+
+class Trie(object):
+ """
+ This is a Trie class, see: https://en.wikipedia.org/wiki/Trie.
+
+ It attempts to follow Pythonic container patterns. See doctests
+ for examples.
+
+ """
+ def __init__(self):
+ self.root = {}
+ self.end = "~#END#~"
+ self.length = 0
+
+ def insert(self, item: Sequence[Any]):
+ """
+ Insert an item.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.__contains__('test')
+ True
+
+ """
+ current = self.root
+ for child in item:
+ current = current.setdefault(child, {})
+ current[self.end] = self.end
+ self.length += 1
+
+ def __contains__(self, item: Sequence[Any]) -> bool:
+ """
+ Check whether an item is in the Trie.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.__contains__('test')
+ True
+ >>> t.__contains__('testing')
+ False
+ >>> 'test' in t
+ True
+
+ """
+ current = self.__traverse__(item)
+ if current is None:
+ return False
+ else:
+ return self.end in current
+
+ def contains_prefix(self, item: Sequence[Any]):
+ """
+ Check whether a prefix is in the Trie. The prefix may or may not
+ be a full item.
+
+ >>> t = Trie()
+ >>> t.insert('testicle')
+ >>> t.contains_prefix('test')
+ True
+ >>> t.contains_prefix('testicle')
+ True
+ >>> t.contains_prefix('tessel')
+ False
+
+ """
+ current = self.__traverse__(item)
+ return current is not None
+
+ def __traverse__(self, item: Sequence[Any]):
+ current = self.root
+ for child in item:
+ if child in current:
+ current = current[child]
+ else:
+ return None
+ return current
+
+ def __getitem__(self, item: Sequence[Any]):
+ """Given an item, return its Trie node which contains all
+ of the successor (child) node pointers. If the item is not
+ a node in the Trie, raise a KeyError.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.insert('testicle')
+ >>> t.insert('tessera')
+ >>> t.insert('tesack')
+ >>> t['tes']
+ {'t': {'~#END#~': '~#END#~', 'i': {'c': {'l': {'e': {'~#END#~': '~#END#~'}}}}}, 's': {'e': {'r': {'a': {'~#END#~': '~#END#~'}}}}, 'a': {'c': {'k': {'~#END#~': '~#END#~'}}}}
+
+ """
+ ret = self.__traverse__(item)
+ if ret is None:
+ raise KeyError(f"Node '{item}' is not in the trie")
+ return ret
+
+ def delete_recursively(self, node, item: Sequence[Any]) -> bool:
+ if len(item) == 1:
+ del node[item]
+ if len(node) == 0 and node is not self.root:
+ del node
+ return True
+ else:
+ return False
+ else:
+ car = item[0]
+ cdr = item[1:]
+ lower = node[car]
+ if self.delete_recursively(lower, cdr):
+ return self.delete_recursively(node, car)
+ return False
+
+ def __delitem__(self, item: Sequence[Any]):
+ """
+ Delete an item from the Trie.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.insert('tess')
+ >>> t.insert('tessel')
+ >>> len(t)
+ 3
+ >>> t.root
+ {'t': {'e': {'s': {'t': {'~#END#~': '~#END#~'}, 's': {'~#END#~': '~#END#~', 'e': {'l': {'~#END#~': '~#END#~'}}}}}}}
+ >>> t.__delitem__('test')
+ >>> len(t)
+ 2
+ >>> t.root
+ {'t': {'e': {'s': {'s': {'~#END#~': '~#END#~', 'e': {'l': {'~#END#~': '~#END#~'}}}}}}}
+ >>> for x in t:
+ ... print(x)
+ tess
+ tessel
+ >>> t.__delitem__('tessel')
+ >>> len(t)
+ 1
+ >>> t.root
+ {'t': {'e': {'s': {'s': {'~#END#~': '~#END#~'}}}}}
+ >>> for x in t:
+ ... print(x)
+ tess
+ >>> t.__delitem__('tess')
+ >>> len(t)
+ 0
+ >>> t.root
+ {}
+ >>> t.insert('testy')
+ >>> len(t)
+ 1
+
+ """
+ if item not in self:
+ raise KeyError(f"Node '{item}' is not in the trie")
+ self.delete_recursively(self.root, item)
+ self.length -= 1
+
+ def __len__(self):
+ """
+ Returns a count of the Trie's item population.
+
+ >>> t = Trie()
+ >>> len(t)
+ 0
+ >>> t.insert('test')
+ >>> len(t)
+ 1
+ >>> t.insert('testicle')
+ >>> len(t)
+ 2
+
+ """
+ return self.length
+
+ def __iter__(self):
+ self.content_generator = self.generate_recursively(self.root, '')
+ return self
+
+ def generate_recursively(self, node, path: Sequence[Any]):
+ """
+ Generate items in the trie one by one.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.insert('tickle')
+ >>> for item in t.generate_recursively(t.root, ''):
+ ... print(item)
+ test
+ tickle
+
+ """
+ for child in node:
+ if child == self.end:
+ yield path
+ else:
+ yield from self.generate_recursively(node[child], path + child)
+
+ def __next__(self):
+ """
+ Iterate through the contents of the trie.
+
+ >>> t = Trie()
+ >>> t.insert('test')
+ >>> t.insert('tickle')
+ >>> for item in t:
+ ... print(item)
+ test
+ tickle
+
+ """
+ ret = next(self.content_generator)
+ if ret is not None:
+ return ret
+ raise StopIteration
+
+ def successors(self, item: Sequence[Any]):
+ """
+ Return a list of the successors of an item.
+
+ >>> t = Trie()
+ >>> t.insert('what')
+ >>> t.insert('who')
+ >>> t.insert('when')
+ >>> t.successors('wh')
+ ['a', 'o', 'e']
+
+ >>> u = Trie()
+ >>> u.insert(['this', 'is', 'a', 'test'])
+ >>> u.insert(['this', 'is', 'a', 'robbery'])
+ >>> u.insert(['this', 'is', 'a', 'walrus'])
+ >>> u.successors(['this', 'is', 'a'])
+ ['test', 'robbery', 'walrus']
+
+ """
+ node = self.__traverse__(item)
+ if node is None:
+ return None
+ return [x for x in node if x != self.end]
+
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()