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
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:
def __init__(self):
self._t = TrieNode("", {})
self._root_node = TrieNode("", {})
def insert(self, word: str) -> None:
prefixes = [word[: i + 1] for i in range(len(word))][:-1]
cur_t = self._t
if len(word) == 0:
return
for i, prefix in enumerate(prefixes):
next_val: TrieNode | None = None
if i + 1 < len(prefixes) - 1:
next_val = TrieNode(prefixes[i + 1], {})
else:
next_val = TrieNode(word, {})
current_node = self._root_node
if cur_t.kids is None:
cur_t.kids = {}
for prefix in [word[: i + 1] for i in range(len(word))]:
current_node.kids.setdefault(prefix, TrieNode(prefix, {}))
current_node = current_node.kids[prefix]
cur_t.kids.setdefault(prefix, next_val)
cur_t = cur_t.kids[prefix]
cur_t.kids["__leaf__"] = TrieNode("__leaf__", {})
leaf = TrieNode.leaf()
current_node.kids[leaf.value] = leaf
def search(self, word: str) -> bool:
return self._has(word, prefix_ok=False)
@ -482,18 +484,17 @@ class Trie:
return self._has(prefix, prefix_ok=True)
def _has(self, word: str, prefix_ok: bool) -> bool:
reverse_path = [word[: i + 1] for i in range(len(word))][::-1]
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:
if len(word) == 0:
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
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()
assert trie.insert("apple") is None
@ -399,3 +407,40 @@ def test_trie():
assert trie.startsWith("app") is True
assert trie.insert("app") is None
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