From 6b468e51f0460af95ae852c6fb8a0b3024b25e56 Mon Sep 17 00:00:00 2001 From: maitriupadhyay03-cell Date: Sat, 6 Jun 2026 16:50:31 +0530 Subject: [PATCH 1/3] fix: correct treap split() to match docstring for equal values (#7854) The split() function in treap.py uses `<` comparison but the docstring states that the right subtree should contain values "greater or equal" to the split value. This fix changes `elif value < root.value:` to `elif value <= root.value:` so that equal values go to the right subtree as documented. Fixes #7854 --- data_structures/binary_tree/treap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 3114c6fa1c26..5c67dc8d7ead 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -5,7 +5,7 @@ class Node: """ - Treap's node + Treap's nodeh Treap is a binary tree by value and heap by priority """ @@ -41,7 +41,7 @@ def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]: """ if root is None or root.value is None: # None tree is split into 2 Nones return None, None - elif value < root.value: + elif value <= root.value: """ Right tree's root will be current node. Now we split(with the same value) current node's left son From 48e35c87ff23f2692c734700669d48280d91e73c Mon Sep 17 00:00:00 2001 From: maitriupadhyay03-cell Date: Sat, 6 Jun 2026 16:52:00 +0530 Subject: [PATCH 2/3] Fix typo in Treap node docstringfix: remove accidental typo in Node class docstring --- data_structures/binary_tree/treap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 5c67dc8d7ead..ca81d5967cb6 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -5,7 +5,7 @@ class Node: """ - Treap's nodeh + Treap's node Treap is a binary tree by value and heap by priority """ From 11481cef8b1187645f94b5e17ad929d72adbd71f Mon Sep 17 00:00:00 2001 From: maitriupadhyay03-cell Date: Sat, 20 Jun 2026 23:39:58 +0530 Subject: [PATCH 3/3] fix(data_structures): handle exact prefix match in RadixNode.insert to fix IndexError Fixes #11316 The insert() method's Case 3 (remaining_prefix == "") would call self.nodes[matching_string[0]].insert(remaining_word) even when remaining_word is empty string. This causes an IndexError in Case 2 when word[0] is accessed on an empty string. Fix: Check if remaining_word is non-empty before recursing. If remaining_word is empty, it means the inserted word exactly matches the prefix of the incoming node, so we just mark that node as a leaf. Also adds a doctest for the reported scenario: insert('fooaaa'), insert('foobbb'), insert('foo') And adds assertions to test_trie() to cover this case. --- data_structures/trie/radix_tree.py | 279 +++++++++++++++-------------- 1 file changed, 146 insertions(+), 133 deletions(-) diff --git a/data_structures/trie/radix_tree.py b/data_structures/trie/radix_tree.py index bd2306befa79..ef1cc85080ac 100644 --- a/data_structures/trie/radix_tree.py +++ b/data_structures/trie/radix_tree.py @@ -6,193 +6,197 @@ class RadixNode: - def __init__(self, prefix: str = "", is_leaf: bool = False) -> None: - # Mapping from the first character of the prefix of the node - self.nodes: dict[str, RadixNode] = {} - - # A node will be a leaf if the tree contains its word - self.is_leaf = is_leaf - - self.prefix = prefix - - def match(self, word: str) -> tuple[str, str, str]: - """Compute the common substring of the prefix of the node and a word - - Args: - word (str): word to compare - - Returns: - (str, str, str): common substring, remaining prefix, remaining word - - >>> RadixNode("myprefix").match("mystring") - ('my', 'prefix', 'string') - """ - x = 0 - for q, w in zip(self.prefix, word): - if q != w: - break - - x += 1 - - return self.prefix[:x], self.prefix[x:], word[x:] - - def insert_many(self, words: list[str]) -> None: - """Insert many words in the tree - - Args: - words (list[str]): list of words - - >>> RadixNode("myprefix").insert_many(["mystring", "hello"]) - """ - for word in words: - self.insert(word) - - def insert(self, word: str) -> None: - """Insert a word into the tree - - Args: - word (str): word to insert - - >>> RadixNode("myprefix").insert("mystring") - - >>> root = RadixNode() - >>> root.insert_many(['myprefix', 'myprefixA', 'myprefixAA']) - >>> root.print_tree() - - myprefix (leaf) - -- A (leaf) - --- A (leaf) - """ - # Case 1: If the word is the prefix of the node - # Solution: We set the current node as leaf - if self.prefix == word and not self.is_leaf: - self.is_leaf = True + def __init__(self, prefix: str = "", is_leaf: bool = False) -> None: + # Mapping from the first character of the prefix of the node + self.nodes: dict[str, RadixNode] = {} + # A node will be a leaf if the tree contains its word + self.is_leaf = is_leaf + self.prefix = prefix + + def match(self, word: str) -> tuple[str, str, str]: + """Compute the common substring of the prefix of the node and a word + + Args: + word (str): word to compare + + Returns: + (str, str, str): common substring, remaining prefix, remaining word + + >>> RadixNode("myprefix").match("mystring") + ('my', 'prefix', 'string') + """ + x = 0 + for q, s in zip(self.prefix, word): + if q != s: + break + x += 1 + + return self.prefix[:x], self.prefix[x:], word[x:] + + def insert_many(self, words: list[str]) -> None: + """Insert many words in the tree + + Args: + words (list[str]): list of words + + >>> RadixNode("myprefix").insert_many(["mystring", "hello"]) + """ + for word in words: + self.insert(word) + + def insert(self, word: str) -> None: + """Insert a word into the tree + + Args: + word (str): word to insert + + >>> RadixNode("myprefix").insert("mystring") + >>> root = RadixNode() + >>> root.insert_many(['myprefix', 'myprefixA', 'myprefixAA']) + >>> root.print_tree() + - myprefix (leaf) + -- A (leaf) + --- A (leaf) + >>> root2 = RadixNode() + >>> root2.insert_many(['fooaaa', 'foobbb', 'foo']) + >>> root2.print_tree() + - foo (leaf) + -- aaa (leaf) + -- bbb (leaf) + """ + # Case 1: If the word is the prefix of the node + # Solution: We set the current node as leaf + if self.prefix == word and not self.is_leaf: + self.is_leaf = True # Case 2: The node has no edges that have a prefix to the word # Solution: We create an edge from the current node to a new one # containing the word - elif word[0] not in self.nodes: +elif word[0] not in self.nodes: self.nodes[word[0]] = RadixNode(prefix=word, is_leaf=True) - else: +else: incoming_node = self.nodes[word[0]] - matching_string, remaining_prefix, remaining_word = incoming_node.match( - word - ) + matching_string, remaining_prefix, remaining_word = incoming_node.match( + word + ) # Case 3: The node prefix is equal to the matching - # Solution: We insert remaining word on the next node + # Solution: We insert remaining word on the next node, or mark as + # leaf if remaining_word is empty (word is a prefix of existing node) if remaining_prefix == "": - self.nodes[matching_string[0]].insert(remaining_word) + if remaining_word: + self.nodes[matching_string[0]].insert(remaining_word) + else: + # The word exactly matches the prefix of the incoming node + self.nodes[matching_string[0]].is_leaf = True # Case 4: The word is greater equal to the matching # Solution: Create a node in between both nodes, change # prefixes and add the new node for the remaining word - else: +else: incoming_node.prefix = remaining_prefix - - aux_node = self.nodes[matching_string[0]] + aux_node = self.nodes[matching_string[0]] self.nodes[matching_string[0]] = RadixNode(matching_string, False) self.nodes[matching_string[0]].nodes[remaining_prefix[0]] = aux_node if remaining_word == "": - self.nodes[matching_string[0]].is_leaf = True - else: + self.nodes[matching_string[0]].is_leaf = True +else: self.nodes[matching_string[0]].insert(remaining_word) def find(self, word: str) -> bool: - """Returns if the word is on the tree + """Returns if the word is on the tree - Args: - word (str): word to check + Args: + word (str): word to check - Returns: - bool: True if the word appears on the tree + Returns: + bool: True if the word appears on the tree - >>> RadixNode("myprefix").find("mystring") - False - """ + >>> RadixNode("myprefix").find("mystring") + False + """ incoming_node = self.nodes.get(word[0], None) if not incoming_node: - return False - else: + return False +else: _matching_string, remaining_prefix, remaining_word = incoming_node.match( - word + word ) # If there is remaining prefix, the word can't be on the tree - if remaining_prefix != "": - return False - # This applies when the word and the prefix are equal - elif remaining_word == "": + if remaining_prefix: + return False + # This applies when the word and the prefix are equal +elif not remaining_word: return incoming_node.is_leaf # We have word remaining so we check the next node - else: +else: return incoming_node.find(remaining_word) def delete(self, word: str) -> bool: - """Deletes a word from the tree if it exists + """Deletes a word from the tree if it exists - Args: - word (str): word to be deleted + Args: + word (str): word to be deleted - Returns: - bool: True if the word was found and deleted. False if word is not found + Returns: + bool: True if the word was found and deleted. False if word is not found - >>> RadixNode("myprefix").delete("mystring") - False - """ + >>> RadixNode("myprefix").delete("mystring") + False + """ incoming_node = self.nodes.get(word[0], None) if not incoming_node: - return False - else: + return False +else: _matching_string, remaining_prefix, remaining_word = incoming_node.match( word ) # If there is remaining prefix, the word can't be on the tree - if remaining_prefix != "": - return False - # We have word remaining so we check the next node - elif remaining_word != "": + if remaining_prefix: + return False + # We have word remaining so we check the next node +elif remaining_word: return incoming_node.delete(remaining_word) # If it is not a leaf, we don't have to delete - elif not incoming_node.is_leaf: +elif not incoming_node.is_leaf: return False - else: +else: # We delete the nodes if no edges go from it - if len(incoming_node.nodes) == 0: - del self.nodes[word[0]] - # We merge the current node with its only child - if len(self.nodes) == 1 and not self.is_leaf: - merging_node = next(iter(self.nodes.values())) - self.is_leaf = merging_node.is_leaf - self.prefix += merging_node.prefix - self.nodes = merging_node.nodes - # If there is more than 1 edge, we just mark it as non-leaf - elif len(incoming_node.nodes) > 1: + if not len(incoming_node.nodes): + del self.nodes[word[0]] + # We merge the current node with its only child + if len(self.nodes) == 1 and not self.is_leaf: + merging_node = next(iter(self.nodes.values())) + self.is_leaf = merging_node.is_leaf + self.prefix += merging_node.prefix + self.nodes = merging_node.nodes + # If there is more than 1 edge, we just mark it as non-leaf +elif len(incoming_node.nodes) > 1: incoming_node.is_leaf = False # If there is 1 edge, we merge it with its child - else: +else: merging_node = next(iter(incoming_node.nodes.values())) - incoming_node.is_leaf = merging_node.is_leaf + incoming_node.is_leaf = merging_node.is_leaf incoming_node.prefix += merging_node.prefix incoming_node.nodes = merging_node.nodes - return True def print_tree(self, height: int = 0) -> None: - """Print the tree + """Print the tree - Args: - height (int, optional): Height of the printed node - """ - if self.prefix != "": - print("-" * height, self.prefix, " (leaf)" if self.is_leaf else "") - - for value in self.nodes.values(): - value.print_tree(height + 1) + Args: + height (int, optional): Height of the printed node + """ + if self.prefix: + print("-" * height, self.prefix, "(leaf)" if self.is_leaf else "") + for value in self.nodes.values(): + value.print_tree(height + 1) def test_trie() -> bool: - words = "banana bananas bandana band apple all beast".split() + words = "banana bananas bandana band apple all beast".split() root = RadixNode() root.insert_many(words) @@ -205,25 +209,34 @@ def test_trie() -> bool: assert not root.find("banana") assert root.find("bananas") + # Test fix for issue #11316: inserting a word that is a prefix of existing words + root2 = RadixNode() + root2.insert("fooaaa") + root2.insert("foobbb") + root2.insert("foo") + assert root2.find("foo"), "foo should be found after insert" + assert root2.find("fooaaa"), "fooaaa should still be found" + assert root2.find("foobbb"), "foobbb should still be found" + return True def pytests() -> None: - assert test_trie() + assert test_trie() def main() -> None: - """ - >>> pytests() - """ + """ + >>> pytests() + """ root = RadixNode() words = "banana bananas bandanas bandana band apple all beast".split() root.insert_many(words) print("Words:", words) print("Tree:") - root.print_tree() + root.print_tree() if __name__ == "__main__": - main() + main()