forked from lua-stdlib/lua-stdlib
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtree.lua
More file actions
285 lines (249 loc) · 7.99 KB
/
tree.lua
File metadata and controls
285 lines (249 loc) · 7.99 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
--[[--
Tree container prototype.
This module returns a table of tree operators, as well as the prototype
for a Tree container object.
This is not a search tree, but rather a way to efficiently store and
retrieve values stored with a path as a key, such as a multi-key
keytable. Although it does have iterators for walking the tree with
various algorithms.
In addition to the functionality described here, Tree containers also
have all the methods and metamethods of the @{std.container.prototype}
(except where overridden here),
Prototype Chain
---------------
table
`-> Container
`-> Tree
@prototype std.tree
]]
local std = require "std.base"
local operator = require "std.operator"
local Container = require "std.container".prototype
local ielems, ipairs, pairs, stdtype =
std.ielems, std.ipairs, std.pairs, std.type
local last = std.base.last
local reduce = std.functional.reduce
local len = std.operator.len
local leaves = std.tree.leaves
local prototype -- forward declaration
--- Tree iterator.
-- @tparam function it iterator function
-- @tparam prototype|table tr tree container or tree-like table
-- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order))
-- @treturn table path to node (`{i1, ...in}`)
-- @treturn node node
local function _nodes (it, tr)
local p = {}
local function visit (n)
if type (n) == "table" then
coroutine.yield ("branch", p, n)
for i, v in it (n) do
p[#p + 1] = i
visit (v)
table.remove (p)
end
coroutine.yield ("join", p, n)
else
coroutine.yield ("leaf", p, n)
end
end
return coroutine.wrap (visit), tr
end
local function clone (t, nometa)
local r = {}
if not nometa then
setmetatable (r, getmetatable (t))
end
local d = {[t] = r}
local function copy (o, x)
for i, v in pairs (x) do
if type (v) == "table" then
if not d[v] then
d[v] = {}
if not nometa then
setmetatable (d[v], getmetatable (v))
end
o[i] = copy (d[v], v)
else
o[i] = d[v]
end
else
o[i] = v
end
end
return o
end
return copy (r, t)
end
local function merge (t, u)
for ty, p, n in _nodes (pairs, u) do
if ty == "leaf" then
t[p] = n
end
end
return t
end
--[[ ============ ]]--
--[[ Tree Object. ]]--
--[[ ============ ]]--
local function X (decl, fn)
return require "std.debug".argscheck ("std.tree." .. decl, fn)
end
--- Tree prototype object.
-- @object prototype
-- @string[opt="Tree"] _type object name
-- @see std.container.prototype
-- @usage
-- local tree = require "std.tree"
-- local Tree = tree.prototype
-- local tr = Tree {}
-- tr[{"branch1", 1}] = "leaf1"
-- tr[{"branch1", 2}] = "leaf2"
-- tr[{"branch2", 1}] = "leaf3"
-- print (tr[{"branch1"}]) --> Tree {leaf1, leaf2}
-- print (tr[{"branch1", 2}]) --> leaf2
-- print (tr[{"branch1", 3}]) --> nil
-- --> leaf1 leaf2 leaf3
-- for leaf in tree.leaves (tr) do
-- io.write (leaf .. "\t")
-- end
prototype = Container {
_type = "Tree",
--- Metamethods
-- @section metamethods
--- Deep retrieval.
-- @function prototype:__index
-- @param i non-table, or list of keys `{i1, ...i_n}`
-- @return `tr[i1]...[i_n]` if *i* is a key list, `tr[i]` otherwise
-- @todo the following doesn't treat list keys correctly
-- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first?
-- @usage
-- del_other_window = keymap[{"C-x", "4", KEY_DELETE}]
__index = function (tr, i)
if stdtype (i) == "table" then
return reduce (operator.get, tr, ielems, i)
else
return rawget (tr, i)
end
end,
--- Deep insertion.
-- @function prototype:__newindex
-- @param i non-table, or list of keys `{i1, ...i_n}`
-- @param[opt] v value
-- @usage
-- function bindkey (keylist, fn) keymap[keylist] = fn end
__newindex = function (tr, i, v)
if stdtype (i) == "table" then
for n = 1, len (i) - 1 do
if stdtype (tr[i[n]]) ~= "Tree" then
rawset (tr, i[n], prototype {})
end
tr = tr[i[n]]
end
rawset (tr, last (i), v)
else
rawset (tr, i, v)
end
end,
}
return std.object.Module {
prototype = prototype,
--- Functions
-- @section functions
--- Make a deep copy of a tree or table, including any metatables.
-- @function clone
-- @tparam table tr tree or tree-like table
-- @tparam boolean nometa if non-`nil` don't copy metatables
-- @treturn prototype|table a deep copy of *tr*
-- @see std.table.clone
-- @see std.object.clone
-- @usage
-- tr = {"one", {two=2}, {{"three"}, four=4}}
-- copy = clone (tr)
-- copy[2].two=5
-- assert (tr[2].two == 2)
clone = X ("clone (table, ?boolean|:nometa)", clone),
--- Tree iterator which returns just numbered leaves, in order.
-- @function ileaves
-- @tparam prototype|table tr tree or tree-like table
-- @treturn function iterator function
-- @treturn prototype|table the tree *tr*
-- @see inodes
-- @see leaves
-- @usage
-- --> t = {"one", "three", "five"}
-- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"}
-- do
-- t[#t + 1] = leaf
-- end
ileaves = X ("ileaves (table)", function (t) return leaves (ipairs, t) end),
--- Tree iterator over numbered nodes, in order.
--
-- The iterator function behaves like @{nodes}, but only traverses the
-- array part of the nodes of *tr*, ignoring any others.
-- @function inodes
-- @tparam prototype|table tr tree or tree-like table to iterate over
-- @treturn function iterator function
-- @treturn tree|table the tree, *tr*
-- @see nodes
inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end),
--- Tree iterator which returns just leaves.
-- @function leaves
-- @tparam table t tree or tree-like table
-- @treturn function iterator function
-- @treturn table *t*
-- @see ileaves
-- @see nodes
-- @usage
-- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"}
-- do
-- t[#t + 1] = leaf
-- end
-- --> t = {2, 4, "five", "foo", "one", "three"}
-- table.sort (t, lambda "=tostring(_1) < tostring(_2)")
leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end),
--- Destructively deep-merge one tree into another.
-- @function merge
-- @tparam table t destination tree
-- @tparam table u table with nodes to merge
-- @treturn table *t* with nodes from *u* merged in
-- @see std.table.merge
-- @usage
-- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}})
merge = X ("merge (table, table)", merge),
--- Tree iterator over all nodes.
--
-- The returned iterator function performs a depth-first traversal of
-- `tr`, and at each node it returns `{node-type, tree-path, tree-node}`
-- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a
-- list of keys used to reach this node, and `tree-node` is the current
-- node.
--
-- Note that the `tree-path` reuses the same table on each iteration, so
-- you must `table.clone` a copy if you want to take a snap-shot of the
-- current state of the `tree-path` list before the next iteration
-- changes it.
-- @function nodes
-- @tparam prototype|table tr tree or tree-like table to iterate over
-- @treturn function iterator function
-- @treturn prototype|table the tree, *tr*
-- @see inodes
-- @usage
-- -- tree = +-- node1
-- -- | +-- leaf1
-- -- | '-- leaf2
-- -- '-- leaf 3
-- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" }
-- for node_type, path, node in nodes (tree) do
-- print (node_type, path, node)
-- end
-- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"}
-- --> "branch" {1} {"leaf1", "leaf"2")
-- --> "leaf" {1,1} "leaf1"
-- --> "leaf" {1,2} "leaf2"
-- --> "join" {1} {"leaf1", "leaf2"}
-- --> "leaf" {2} "leaf3"
-- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"}
-- os.exit (0)
nodes = X ("nodes (table)", function (t) return _nodes (pairs, t) end),
}