def test_child_methods(self): # Create node 'A' and verify it does not have any children node_A = PrefixTreeNode('A') assert node_A.num_children() == 0 assert node_A.has_child('B') is False # Verify getting child from node 'A' raises error with self.assertRaises(ValueError): node_A.get_child('B') # Create node 'B' and add it as child to node 'A' node_B = PrefixTreeNode('B') node_A.add_child('B', node_B) # Verify node 'A' has node 'B' as child assert node_A.num_children() == 1 assert node_A.has_child('B') is True assert node_A.get_child('B') is node_B # Verify adding node 'B' as child to node 'A' again raises error with self.assertRaises(ValueError): node_A.add_child('B', node_B) # Create node 'C' and add it as another child to node 'A' node_C = PrefixTreeNode('C') node_A.add_child('C', node_C) # Verify node 'A' has both nodes 'B' and 'C' as children assert node_A.num_children() == 2 assert node_A.has_child('B') is True assert node_A.has_child('C') is True assert node_A.get_child('C') is node_C # Verify adding node 'C' as child to node 'A' again raises error with self.assertRaises(ValueError): node_A.add_child('C', node_C)
def test_init_and_properties(self): character = 'a' node = PrefixTreeNode(character) # Verify node character assert isinstance(node.character, str) print(node.character) assert node.character == 'a' # Verify children nodes structure assert isinstance(node.children, PrefixTreeNode.CHILDREN_TYPE) assert len(node.children) == 26 assert node.children == [None] * 26 assert node.num_children() == 0 # Verify terminal boolean assert node.terminal is False assert node.is_terminal() is False
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 def contains(self, string): """Return True if this prefix tree contains the given string.""" node = _find_node(string) if node not None: return True return False
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 def contains(self, string): """Return True if this prefix tree contains the given string.""" if len(string) == 0: return True node = self.root for character in string.lower(): index = ord(character) - 97 if node.children[index] is not None: node = node.children[index] else: return False return node.terminal def insert(self, string): """Insert the given string into this prefix tree.""" node = self.root for character in string.lower(): if character == '-': continue index = ord(character) - 97 if node.children[index] is None: node.children[index] = PrefixTreeNode(character) node = node.children[index] else: node = node.children[index] if not node.terminal: node.terminal = True self.size += 1 def _find_node(self, string): """Return a pair containing the deepest node in this prefix tree that matches the longest prefix of the given string and the node's depth. The depth returned is equal to the number of prefix characters matched. Search is done iteratively with a loop starting from the root node.""" # Match the empty string if len(string) == 0: return self.root, 0 # Start with the root node node = self.root depth = 0 for character in string.lower(): if character == '-': continue index = ord(character) - 97 if node.children[index] is not None: node = node.children[index] depth += 1 else: return None, depth return node, depth def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree node = self._find_node(prefix)[0] if node is None: return [] completions = [] if node.terminal: completions.append(prefix) self._traverse(node, prefix, completions.append) return completions def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree all_strings = [] self._traverse(self.root, PrefixTree.START_CHARACTER, all_strings.append) return all_strings def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. (pre-order) Start at the given node and visit each node with the given function.""" for child in node.children: if child is not None: if child.terminal: visit(prefix + child.character) self._traverse(child, prefix + child.character, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '^' END_CHARACTER = '$' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert terminal character self.insert('') self.size -= 1 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings). Time: Θ(1) | Space: Θ(1) """ return self.root.num_children() == 1 def contains(self, string): """Return True if this prefix tree contains the given string. Time: O(kn) n = # of strings (width of tree), k = len(string) Space: Θ(1) """ cur_node = self.root for char in string + '$': try: cur_node = cur_node.get_child(char) # Update cur_node except ValueError: ## char wasn't found, so the exact string doesn't exist return False return True def insert(self, string): """Insert the given string into this prefix tree. Time: O(kn) n = # of strings (width of tree), k = len(string) Space: Θ(1) """ cur_node = self.root for char in string: try: ## Try to update cur_node with existing child for char cur_node = cur_node.get_child(char) except ValueError: ## Child for char doesn't exist, so add new child then update ## cur_child new_node = PrefixTreeNode(char) cur_node.add_child(char, new_node) cur_node = new_node try: ## Check if terminate character exists cur_node.get_child('$') except ValueError: ## Terminate character ($) wasn't found, so this must be a new ## string cur_node.add_child('$', PrefixTreeNode('$')) # Terminal node self.size += 1 # Increment because a new string has been inserted def _find_node(self, string): """Return a pair containing the deepest node in this prefix tree that matches the longest prefix of the given string and the node's depth. The depth returned is equal to the number of prefix characters matched. Search is done iteratively with a loop starting from the root node. Time: O(kn) n = # of strings (width of tree), k = len(string) Space: Θ(1) """ # Start with the root node node = self.root depth = 0 for char in string: try: node = node.get_child(char) # Update node with child depth += 1 except ValueError: ## Child node wasn't found. End of prefix has been reached break return node, depth def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string. Time: O(n) | Space: O(n)""" # Create a list of completions in prefix tree completions = [] ## BFS Search for all completions (maintain relative order of nodes) node, depth = self._find_node(prefix) if depth < len(prefix): return [] q = deque([(prefix, node)]) while len(q): cur_string, node = q.pop() for child in node.children: if child.character == '$': ## End of string found completions.append(cur_string) else: q.appendleft((cur_string + child.character, child)) return completions def strings(self): """Return a list of all strings stored in this prefix tree. Time: Θ(n) | Space: O(n)""" # Create a list of all strings in prefix tree all_strings = [] q = deque([('', self.root)]) while len(q): cur_string, node = q.pop() for child in node.children: if cur_string != '' and child.character == '$': all_strings.append(cur_string) else: q.appendleft((cur_string + child.character, child)) return all_strings def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. Start at the given node with the given prefix representing its path in this prefix tree and visit each node with the given visit function. Time: Θ(n) | Space: O(lg n)""" if child: for child in node.children: visit(child) self._traverse(child, prefix + child.character, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 def contains(self, string): """Return True if this prefix tree contains the given string.""" node, _ = self._find_node(string) if node is not None: return node.is_terminal() else: return False def insert(self, string): """Insert the given string into this prefix tree.""" node = self.root added = False for c in string: if not node.has_child(c): node.add_child(c, PrefixTreeNode(c)) node = node.get_child(c) added = True else: node = node.get_child(c) node.terminal = True if added: self.size += 1 def _find_node(self, string): """Return a tuple containing the node that terminates the given string in this prefix tree and the node's depth, or if the given string is not completely found, return None and the depth of the last matching node. Search is done iteratively with a loop starting from the root node.""" # Match the empty string if len(string) == 0: return self.root, 0 # Start with the root node node = self.root depth = 0 for c in string: if node.has_child(c): node = node.get_child(c) depth += 1 else: return None, depth return node, depth def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree completions = [] node, _ = self._find_node(prefix) def visit(n, p): if n.is_terminal(): completions.append(p) if node is not None: self._traverse(node, prefix, visit) return completions def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree return self.complete('') def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. Start at the given node and visit each node with the given function.""" visit(node, prefix) for child in node.all_children(): self._traverse(child, prefix + child.character, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 def contains(self, string): """Return True if this prefix tree contains the given string.""" current = self.root for character in string: if not current.has_child(character): return False current = current.get_child(character) if current.terminal is True: return True return False def insert(self, string): """Insert the given string into this prefix tree.""" current = self.root new_string = False for character in string: if not current.has_child(character): node = PrefixTreeNode(character) current.add_child(character, node) new_string = True current = current.get_child(character) if new_string: # In case they try to add the same string self.size += 1 current.terminal = True def _find_node(self, string): """Return a pair containing the deepest node in this prefix tree that matches the longest prefix of the given string and the node's depth. The depth returned is equal to the number of prefix characters matched. Search is done iteratively with a loop starting from the root node.""" # Match the empty string if len(string) == 0: return self.root, 0 # Start with the root node current = self.root depth = 0 for character in string: if not current.has_child(character): return None, depth current = current.get_child(character) depth += 1 return current, depth def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree completions = [] starting_node, depth = self._find_node(prefix) if starting_node is not None: self._traverse(starting_node, prefix[:-1], completions.append) return completions def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree return self.complete('') def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. Start at the given node with the given prefix representing its path in this prefix tree and visit each node with the given visit function.""" prefix += node.character if node.terminal: visit(prefix) for child in node.children.values(): self._traverse(child, prefix, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" # TODO if self.root.num_children() == 0: return True return False def contains(self, string): """Return True if this prefix tree contains the given string.""" # TODO node = self.root for i in range(len(string)): if (i == len(string) - 1) and (node.has_child(string[i])): node = node.get_child(string[i]) if node.terminal == True: return True else: return False elif (node.has_child(string[i])): node = node.get_child(string[i]) else: return False return True def insert(self, string): """Insert the given string into this prefix tree.""" # TODO if not self.contains(string): self.size += 1 node = self.root for i in range(len(string)): if not node.has_child(string[i]): new_node = PrefixTreeNode(string[i]) node.add_child(string[i], new_node) node = node.get_child(string[i]) if i == len(string) - 1: node.terminal = True def _find_node(self, string): """Return a pair containing the deepest node in this prefix tree that matches the longest prefix of the given string and the node's depth. The depth returned is equal to the number of prefix characters matched. Search is done iteratively with a loop starting from the root node.""" # Match the empty string if len(string) == 0: return self.root, 0 # Start with the root node node = self.root # TODO depth = 0 for i in range(len(string)): if node.has_child(string[i]): node = node.get_child(string[i]) depth += 1 return node, depth def _get_string(self, node, curr_string, strings): first_node = node for child in node.children: node = node.get_child(child) curr_string += node.character if node.terminal == True: strings.append(curr_string) self._get_string(node, curr_string, strings) node = first_node return strings def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree completions = [] # TODO node, depth = self._find_node(prefix) # early exit if depth < len(prefix): return completions self._traverse(node, prefix[:depth], completions.append) return completions def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree all_strings = [] # TODO self._traverse(self.root, '', all_strings.append) return all_strings def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. Start at the given node with the given prefix representing its path in this prefix tree and visit each node with the given visit function.""" # TODO if node.terminal == True: visit(prefix) for child in node.children: temp_node = node.get_child(child) self._traverse(temp_node, prefix + temp_node.character, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's root node START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 def contains(self, string): """Return True if this prefix tree contains the given string.""" if self._find_terminal_node(string)[0] != None: return True else: return False def insert(self, string): """Insert the given string into this prefix tree.""" if self.contains(string): return # Insert string into tree curr_node = self.root for indx, char in enumerate(string): # if char DNE in tree, increase size # Traverse to next child in char is alread in string if curr_node.has_child(char): curr_node = curr_node.get_child(char) # Otherwise, add the child else: curr_node.add_child(char, PrefixTreeNode(char)) curr_node = curr_node.get_child(char) # Change last node in str to terminal if indx == len(string) - 1: curr_node.terminal = True self.size += 1 def _find_node(self, string): """Return a pair containing the deepest node in this prefix tree that matches the longest prefix of the given string and the node's depth. The depth returned is equal to the number of prefix characters matched. Search is done iteratively with a loop starting from the root node.""" depth = 0 # Match the empty string if len(string) == 0: return self.root, depth # Start with the root node curr_node = self.root for char in string: if curr_node.has_child(char): curr_node = curr_node.get_child(char) depth += 1 else: # If full prefix is not found return depth of last found node return None, depth # if prefix is found, return node and depth if curr_node.is_terminal(): return curr_node, depth # Otherwise return none and depth return curr_node, depth def _find_terminal_node(self, string): """Return a tuple containing the node that terminates the given string in this prefix tree and the node's depth, or if the given string is not completely found, return None and the depth of the last matching node. Search is done iteratively with a loop starting from the root node.""" depth = 0 # Match the empty string if len(string) == 0: return self.root, depth # Start with the root node curr_node = self.root for char in string: if curr_node.has_child(char): curr_node = curr_node.get_child(char) depth += 1 else: # If full prefix is not found return depth of last found node return None, depth # if prefix is found, return node and depth if curr_node.is_terminal(): return curr_node, depth # Otherwise return none and depth return None, depth def complete(self, prefix=''): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree completions = [] node = self._find_node(prefix)[0] if node == None: return completions # Traverse through tree to complete prefix if not self.is_empty(): self._traverse(node, prefix, completions.append) return completions def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree return self.complete() def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal. Start at the given node and visit each node with the given function.""" if node.is_terminal() and node is not None: visit(prefix) for child_id in node.children: child = node.get_child(child_id) if child: self._traverse(child, prefix + child.character, visit)
class PrefixTree: """PrefixTree: A multi-way prefix tree that stores strings with efficient methods to insert a string into the tree, check if it contains a matching string, and retrieve all strings that start with a given prefix string. Time complexity of these methods depends only on the number of strings retrieved and their maximum length (size and height of subtree searched), but is independent of the number of strings stored in the prefix tree, as its height depends only on the length of the longest string stored in it. This makes a prefix tree effective for spell-checking and autocompletion. Each string is stored as a sequence of characters along a path from the tree's root node to a terminal node that marks the end of the string.""" # Constant for the start character stored in the prefix tree's ROOT NODE START_CHARACTER = '' def __init__(self, strings=None): """Initialize this prefix tree and insert the given strings, if any.""" # Create a new root node with the start character self.root = PrefixTreeNode(PrefixTree.START_CHARACTER) # Count the number of strings inserted into the tree self.size = 0 # Insert each string, if any were given if strings is not None: for string in strings: self.insert(string) def __repr__(self): """Return a string representation of this prefix tree.""" return f'PrefixTree({self.strings()!r})' def is_empty(self): """Return True if this prefix tree is empty (contains no strings).""" return self.root.num_children() == 0 # return self.size == 0 #maybe? def contains(self, string): """Return True if this prefix tree contains the given string.""" return self.root.has_child(string) def insert(self, string): """Insert the given string into this prefix tree.""" self.root.add_child(string) def _find_node(self, string): """Return a tuple containing the node that terminates the given string in this prefix tree and the node's depth, or if the given string is not completely found, return None and the depth of the last matching node. Search is done iteratively with a loop starting from the root node.""" # Match the empty string if len(string) == 0: return self.root, 0 # Start with the root node node = self.root # TODO def complete(self, prefix): """Return a list of all strings stored in this prefix tree that start with the given prefix string.""" # Create a list of completions in prefix tree completions = [] # TODO def strings(self): """Return a list of all strings stored in this prefix tree.""" # Create a list of all strings in prefix tree all_strings = [] # TODO def _traverse(self, node, prefix, visit): """Traverse this prefix tree with recursive depth-first traversal.