Fully un-busted yet slow Trie

This commit is contained in:
Dan Buch 2023-10-27 08:19:03 -04:00
parent 43a2e51712
commit efc1c8f029
Signed by: meatballhat
GPG Key ID: A12F782281063434
2 changed files with 74 additions and 28 deletions

View File

@ -451,29 +451,31 @@ class TrieNode(typing.NamedTuple):
value: str value: str
kids: dict[str, "TrieNode"] kids: dict[str, "TrieNode"]
@property
def is_leaf(self) -> bool:
return "__self__" in self.kids
@classmethod
def leaf(cls) -> "TrieNode":
return cls("__self__", {})
class Trie: class Trie:
def __init__(self): def __init__(self):
self._t = TrieNode("", {}) self._root_node = TrieNode("", {})
def insert(self, word: str) -> None: def insert(self, word: str) -> None:
prefixes = [word[: i + 1] for i in range(len(word))][:-1] if len(word) == 0:
cur_t = self._t return
for i, prefix in enumerate(prefixes): current_node = self._root_node
next_val: TrieNode | None = None
if i + 1 < len(prefixes) - 1:
next_val = TrieNode(prefixes[i + 1], {})
else:
next_val = TrieNode(word, {})
if cur_t.kids is None: for prefix in [word[: i + 1] for i in range(len(word))]:
cur_t.kids = {} current_node.kids.setdefault(prefix, TrieNode(prefix, {}))
current_node = current_node.kids[prefix]
cur_t.kids.setdefault(prefix, next_val) leaf = TrieNode.leaf()
cur_t = cur_t.kids[prefix] current_node.kids[leaf.value] = leaf
cur_t.kids["__leaf__"] = TrieNode("__leaf__", {})
def search(self, word: str) -> bool: def search(self, word: str) -> bool:
return self._has(word, prefix_ok=False) return self._has(word, prefix_ok=False)
@ -482,18 +484,17 @@ class Trie:
return self._has(prefix, prefix_ok=True) return self._has(prefix, prefix_ok=True)
def _has(self, word: str, prefix_ok: bool) -> bool: def _has(self, word: str, prefix_ok: bool) -> bool:
reverse_path = [word[: i + 1] for i in range(len(word))][::-1] if len(word) == 0:
cur_t = self._t
value = cur_t.value
is_leaf: bool = False
while reverse_path and cur_t is not None:
value = cur_t.value
is_leaf = "__leaf__" in cur_t.kids
cur_t = cur_t.kids.get(reverse_path.pop())
if prefix_ok and cur_t is not None and value == word:
return True return True
return (cur_t is None or is_leaf) and value == word reverse_path = [word[: i + 1] for i in range(len(word))][::-1]
current_node = self._root_node
while reverse_path and current_node is not None:
current_node = current_node.kids.get(reverse_path.pop())
return (
current_node is not None
and (current_node.is_leaf or prefix_ok)
and current_node.value == word
)

View File

@ -390,7 +390,15 @@ def test_randomized_set(cls: type[stuff.RandomizedSet] | type[stuff.SlowRandomiz
assert inst.getRandom() == 2 assert inst.getRandom() == 2
def test_trie(): def test_trie_single_letter():
trie = stuff.Trie()
assert trie.insert("a") is None
assert trie.search("a") is True
assert trie.startsWith("a") is True
def test_trie_prefix_leaf():
trie = stuff.Trie() trie = stuff.Trie()
assert trie.insert("apple") is None assert trie.insert("apple") is None
@ -399,3 +407,40 @@ def test_trie():
assert trie.startsWith("app") is True assert trie.startsWith("app") is True
assert trie.insert("app") is None assert trie.insert("app") is None
assert trie.search("app") is True assert trie.search("app") is True
def test_trie_two_letter():
trie = stuff.Trie()
assert trie.insert("ab") is None
assert trie.search("a") is False
assert trie.startsWith("a") is True
def test_trie_busy():
trie = stuff.Trie()
assert trie.insert("app") is None
assert trie.insert("apple") is None
assert trie.insert("beer") is None
assert trie.insert("add") is None
assert trie.insert("jam") is None
assert trie.insert("rental") is None
assert trie.search("apps") is False
assert trie.search("app") is True
assert trie.search("ad") is False
assert trie.search("applepie") is False
assert trie.search("rest") is False
assert trie.search("jan") is False
assert trie.search("rent") is False
assert trie.search("beer") is True
assert trie.search("jam") is True
assert trie.startsWith("apps") is False
assert trie.startsWith("app") is True
assert trie.startsWith("ad") is True
assert trie.startsWith("applepie") is False
assert trie.startsWith("rest") is False
assert trie.startsWith("jan") is False
assert trie.startsWith("rent") is True
assert trie.startsWith("beer") is True
assert trie.startsWith("jam") is True