Make trie method way faster via "caching"
dkl9

dkl9 commited on 2025-202 20:54:00
Showing 2 changed files, with 19 additions and 17 deletions.

... ...
@@ -0,0 +1,2 @@
1
+*.txt
2
+tags
... ...
@@ -1,6 +1,7 @@
1 1
 from collections import defaultdict
2 2
 import re
3 3
 import sys
4
+import time
4 5
 from typing import Generator
5 6
 
6 7
 def minima(options, key):
... ...
@@ -15,15 +16,18 @@ def minima(options, key):
15 16
             result.add(o)
16 17
     return result
17 18
 
18
-def cost(token: str, child: "Trie") -> tuple:
19
-    weight = child.deep_weight()
20
-    return child.deep_usage() / weight, -weight, len(token), token
19
+def cost(token_child: tuple[str, "Trie"]) -> tuple:
20
+    token, child = token_child
21
+    weight = child.deep_weight
22
+    return child.deep_usage / weight, -weight, len(token), token
21 23
 
22 24
 class Trie:
23 25
     def __init__(self, weight: int = 0):
24 26
         self.children: defaultdict[str, Trie] = defaultdict(Trie)
25 27
         self.weight: int = weight
28
+        self.deep_weight: int = weight
26 29
         self.usage: int = 0
30
+        self.deep_usage: int = 0
27 31
 
28 32
     def __str__(self) -> str:
29 33
         s = f"[{self.usage}/{self.weight}]"
... ...
@@ -32,25 +36,17 @@ class Trie:
32 36
         return s
33 37
 
34 38
     def add(self, word: list[str], weight: int = 1):
39
+        self.deep_weight += weight
35 40
         if word:
36 41
             self.children[word[0]].add(word[1:], weight)
37 42
         else:
38 43
             self.weight += weight
39 44
 
40
-    def deep_weight(self) -> int:
41
-        return self.weight + sum(c.deep_weight() for c in self.children.values())
42
-
43
-    def deep_usage(self) -> int:
44
-        return self.usage + sum(c.deep_usage() for c in self.children.values())
45
-
46 45
     def best(self) -> tuple[list[str], "Trie"]:
47 46
         if self.weight > self.usage:
48 47
             return [], self
49 48
         elif self.children:
50
-            token, child = min(
51
-                self.children.items(),
52
-                key=lambda kc: cost(*kc)
53
-            )
49
+            token, child = min(self.children.items(), key=cost)
54 50
             suffix, leaf = child.best()
55 51
             if not leaf:
56 52
                 return None, None
... ...
@@ -58,7 +54,11 @@ class Trie:
58 54
         else:
59 55
             return None, None
60 56
 
61
-    def use(self):
57
+    def use(self, word: list[str]):
58
+        self.deep_usage += 1
59
+        if word:
60
+            self.children[word[0]].use(word[1:])
61
+        else:
62 62
             assert self.usage < self.weight
63 63
             self.usage += 1
64 64
 
... ...
@@ -66,16 +66,16 @@ class Trie:
66 66
         while True:
67 67
             tokens, leaf = self.best()
68 68
             if not leaf:
69
-                assert self.deep_usage() == self.deep_weight()
69
+                assert self.deep_usage == self.deep_weight
70 70
                 break
71 71
             yield tokens
72
-            leaf.use()
72
+            self.use(tokens)
73 73
 
74 74
 re_flags = re.MULTILINE | re.DOTALL
75 75
 token_pattern = "[-_ \t]*[^-_\\s]+"
76 76
 entry_pattern = re.compile(f"({token_pattern})+", re_flags)
77 77
 token_pattern = re.compile(token_pattern, re_flags)
78
-words = (re.findall(token_pattern, m.group(0)) for m in re.finditer(entry_pattern, sys.stdin.read()))
78
+words = [re.findall(token_pattern, m.group(0)) for m in re.finditer(entry_pattern, sys.stdin.read())]
79 79
 t = Trie()
80 80
 for w in words:
81 81
     t.add(w)
82 82