-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautocomplete.py
More file actions
130 lines (112 loc) · 3.25 KB
/
autocomplete.py
File metadata and controls
130 lines (112 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/python3
# Questioner: Twitter
# Difficulty: Medium
"""
Implement an autocomplete system. That is, given a query string s and a set of all possible query strings, return all strings in the set that have s as a prefix.
For example, given the query string de and the set of strings [dog, deer, deal], return [deer, deal].
Hint: Try preprocessing the dictionary into a more efficient data structure to speed up queries.
"""
def simple(q, arr, prep=None):
return [e for e in arr if e.startswith(q)]
# ------------------------------------------------------------------------------
import math
def narrow_prep(arr):
return sorted(arr)
def narrow(q, arr, prep=None):
if prep == None:
prep = narrow_prep(arr)
low, high = narrow_search(q, prep)
return prep[low : high] if low < len(arr) else []
def narrow_search(q, arr):
low = 0
high = len(arr)
for i in range(len(q)):
midh = high
while True:
midl = low + int((midh - low) / 2)
if low == midl:
break
if len(arr[midl]) <= i or arr[midl][i] < q[i]:
low = midl
else:
midh = midl
if len(arr[midl]) <= i or arr[midl][i] < q[i]:
low += 1
if len(arr) <= low:
break
midl += 1
while True:
midh = midl + int((high - midl) / 2)
if midl == midh:
break
if len(arr[midh]) <= i or arr[midh][i] <= q[i]:
midl = midh
else:
high = midh
if len(arr[midh]) > i and arr[midh][i] > q[i]:
high -= 1
if low == high:
break
return low, high
# ------------------------------------------------------------------------------
TERMINATOR = '\0'
def tree_prep(arr):
root = dict()
for s in arr:
node = root
for c in s:
if not c in node:
node[c] = dict()
node = node[c]
node[TERMINATOR] = True
return root
def tree(q, arr, prep=None):
if prep == None:
prep = tree_prep(arr)
node = prep
for c in q:
if c in node:
node = node[c]
else:
return []
res = tree_result(node)
return map(lambda s: q + s, res)
def tree_result(node):
res = []
for c in node:
if c == TERMINATOR:
res.append("")
else:
inner_res = tree_result(node[c])
res.extend(map(lambda s: c + s, inner_res))
return res
# ------------------------------------------------------------------------------
if __name__ == '__main__':
import random
random.seed(a="almost random")
tests = [
(["dog", "deer", "deal"], {"de": ["deal", "deer"]})
]
def word(max_length = 7): return ''.join(random.choices('abcde', k=random.randint(1, max_length)))
for i in range(50):
arr = set()
for j in range(random.randint(10, 250)):
arr.add(word())
test = dict()
for j in range(50):
q = word(4)
expected = sorted(simple(q, arr))
test[q] = expected
tests.append((arr, test))
func = tree
prep_func = tree_prep
for i, test in enumerate(tests):
arr = test[0]
prep = prep_func(arr)
for q in test[1]:
expected = test[1][q]
actual = sorted(func(q, arr, prep))
if expected != actual:
print('\n'.join([f"{i}: {v}" for i, v in enumerate(sorted(arr))]))
assert expected == actual, f"Test {i} failed: q: {q}\n\texpected: {expected}\n\tactual : {actual}"
print("All tests passed")