-
Notifications
You must be signed in to change notification settings - Fork 9
/
trie.lua
133 lines (123 loc) · 3.48 KB
/
trie.lua
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
131
132
133
-- Localize globals
local math, next, pairs, setmetatable, string, table, unpack = math, next, pairs, setmetatable, string, table, unpack
-- Set environment
local _ENV = {}
setfenv(1, _ENV)
local metatable = {__index = _ENV}
-- Setting the metatable is fine as it does not contain single-character keys.
-- TODO (?) encapsulate in "root" field for better code quality?
function new(table) return setmetatable(table or {}, metatable) end
function insert(self, word, value, overwrite)
for i = 1, word:len() do
local char = word:sub(i, i)
self[char] = self[char] or {}
self = self[char]
end
local previous_value = self.value
if not previous_value or overwrite then self.value = value or true end
return previous_value
end
function remove(self, word)
local branch, character = self, word:sub(1, 1)
for i = 1, word:len() - 1 do
local char = word:sub(i, i)
if not self[char] then return end
if self[char].value or next(self, next(self)) then
branch = self
character = char
end
self = self[char]
end
local char = word:sub(word:len())
if not self[char] then return end
self = self[char]
local previous_value = self.value
self.value = nil
if branch and not next(self) then branch[character] = nil end
return previous_value
end
--> value if found
--> nil else
function get(self, word)
for i = 1, word:len() do
local char = word:sub(i, i)
self = self[char]
if not self then return end
end
return self.value
end
function suggestion(self, remainder)
local until_now = {}
local subtries = { [self] = until_now }
local suggestion, value
while next(subtries) do
local new_subtries = {}
local leaves = {}
for trie, word in pairs(subtries) do
if trie.value then table.insert(leaves, { word = word, value = trie.value }) end
end
if #leaves > 0 then
if remainder then
local best_leaves = {}
local best_score = 0
for _, leaf in pairs(leaves) do
local score = 0
for i = 1, math.min(#leaf.word, string.len(remainder)) do
-- calculate intersection
if remainder:sub(i, i) == leaf.word[i] then score = score + 1 end
end
if score == best_score then table.insert(best_leaves, leaf)
elseif score > best_score then best_leaves = { leaf } end
end
leaves = best_leaves
end
-- TODO select best instead of random
local leaf = leaves[math.random(1, #leaves)]
suggestion, value = table.concat(leaf.word), leaf.value
break
end
for trie, word in pairs(subtries) do
for char, subtrie in pairs(trie) do
local word = { unpack(word) }
table.insert(word, char)
new_subtries[subtrie] = word
end
end
subtries = new_subtries
end
return suggestion, value
end
--> value if found
--> nil, suggestion, value of suggestion else
function search(self, word)
for i = 1, word:len() do
local char = word:sub(i, i)
if not self[char] then
local until_now = word:sub(1, i - 1)
local suggestion, value = suggestion(self, word:sub(i))
return nil, until_now .. suggestion, value
end
self = self[char]
end
local value = self.value
if value then return value end
local until_now = word
local suggestion, value = suggestion(self)
return nil, until_now .. suggestion, value
end
function find_longest(self, query, query_offset)
local leaf_pos = query_offset
local last_leaf
for i = query_offset, query:len() do
local char = query:sub(i, i)
self = self[char]
if not self then break
elseif self.value then
last_leaf = self.value
leaf_pos = i
end
end
return last_leaf, leaf_pos
end
-- Export environment
return _ENV