radix_tree 1.0.0 → 1.1.0.dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +2 -6
- data/lib/radix_tree.rb +80 -77
- metadata +4 -10
data/README
CHANGED
@@ -7,17 +7,13 @@ to avoid DoS via Algorithmic Complexity Attacks.
|
|
7
7
|
|
8
8
|
== Performance
|
9
9
|
|
10
|
-
*
|
10
|
+
* 20 times slower for 10 bytes key, 100000 elements insertion
|
11
11
|
* 10 times slower for 10 bytes key, 100000 elements retrieval
|
12
12
|
|
13
13
|
|
14
14
|
== TODO
|
15
15
|
|
16
|
-
|
17
|
-
* find predecessor
|
18
|
-
* find successor
|
19
|
-
* find_all by start string
|
20
|
-
* delete_all by start string
|
16
|
+
See comments in lib/radix_tree.rb
|
21
17
|
|
22
18
|
|
23
19
|
== Author
|
data/lib/radix_tree.rb
CHANGED
@@ -30,11 +30,16 @@ class RadixTree
|
|
30
30
|
class Node
|
31
31
|
UNDEFINED = Object.new
|
32
32
|
|
33
|
-
attr_reader :key, :
|
33
|
+
attr_reader :key, :index
|
34
|
+
attr_reader :value
|
34
35
|
attr_reader :children
|
35
36
|
|
36
|
-
def initialize(key, value = UNDEFINED, children = nil)
|
37
|
-
@key, @value, @children = key, value, children
|
37
|
+
def initialize(key, index, value = UNDEFINED, children = nil)
|
38
|
+
@key, @index, @value, @children = key, index, value, children
|
39
|
+
end
|
40
|
+
|
41
|
+
def label
|
42
|
+
@key[0, @index]
|
38
43
|
end
|
39
44
|
|
40
45
|
def undefined?
|
@@ -54,49 +59,63 @@ class RadixTree
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
def each(
|
58
|
-
prefix += @key
|
62
|
+
def each(&block)
|
59
63
|
if @value != UNDEFINED
|
60
|
-
block.call
|
64
|
+
block.call [label, @value]
|
61
65
|
end
|
62
66
|
if @children
|
63
|
-
@children.
|
64
|
-
child.each(
|
67
|
+
@children.each_value do |child|
|
68
|
+
child.each(&block)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
72
|
+
|
73
|
+
def each_key
|
74
|
+
each do |k, v|
|
75
|
+
yield k
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def each_value
|
80
|
+
each do |k, v|
|
81
|
+
yield v
|
82
|
+
end
|
83
|
+
end
|
68
84
|
|
69
|
-
def keys
|
70
|
-
collect
|
85
|
+
def keys
|
86
|
+
collect { |k, v| k }
|
71
87
|
end
|
72
88
|
|
73
|
-
def values
|
74
|
-
collect
|
89
|
+
def values
|
90
|
+
collect { |k, v| v }
|
75
91
|
end
|
76
92
|
|
77
93
|
def store(key, value)
|
78
|
-
if
|
94
|
+
if same_key?(key)
|
79
95
|
@value = value
|
80
96
|
else
|
81
|
-
|
82
|
-
if
|
83
|
-
push(key
|
97
|
+
pos = head_match_length(key)
|
98
|
+
if pos == @index
|
99
|
+
push(key, value)
|
84
100
|
else
|
85
|
-
split(
|
86
|
-
|
87
|
-
|
101
|
+
split(pos)
|
102
|
+
if same_key?(key)
|
103
|
+
@value = value
|
104
|
+
else
|
105
|
+
push(key, value)
|
106
|
+
end
|
88
107
|
end
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
111
|
def retrieve(key)
|
93
|
-
if
|
112
|
+
if same_key?(key)
|
94
113
|
@value
|
95
114
|
elsif !@children
|
96
115
|
UNDEFINED
|
97
116
|
else
|
98
|
-
|
99
|
-
if child = find_child(key)
|
117
|
+
pos = head_match_length(key)
|
118
|
+
if child = find_child(key[pos])
|
100
119
|
child.retrieve(key)
|
101
120
|
else
|
102
121
|
UNDEFINED
|
@@ -105,14 +124,14 @@ class RadixTree
|
|
105
124
|
end
|
106
125
|
|
107
126
|
def delete(key)
|
108
|
-
if
|
127
|
+
if same_key?(key)
|
109
128
|
value, @value = @value, UNDEFINED
|
110
129
|
value
|
111
130
|
elsif !@children
|
112
131
|
nil
|
113
132
|
else
|
114
|
-
|
115
|
-
if child = find_child(key)
|
133
|
+
pos = head_match_length(key)
|
134
|
+
if child = find_child(key[pos])
|
116
135
|
value = child.delete(key)
|
117
136
|
if value and child.undefined?
|
118
137
|
reap(child)
|
@@ -122,44 +141,46 @@ class RadixTree
|
|
122
141
|
end
|
123
142
|
end
|
124
143
|
|
125
|
-
def dump_tree(io,
|
126
|
-
prefix += @key
|
144
|
+
def dump_tree(io, indent = '')
|
127
145
|
indent += ' '
|
128
146
|
if undefined?
|
129
|
-
io << sprintf("#<%s:0x%010x %s>", self.class.name, __id__,
|
147
|
+
io << sprintf("#<%s:0x%010x %s>", self.class.name, __id__, label.inspect)
|
130
148
|
else
|
131
|
-
io << sprintf("#<%s:0x%010x %s> => %s", self.class.name, __id__,
|
149
|
+
io << sprintf("#<%s:0x%010x %s> => %s", self.class.name, __id__, label.inspect, @value.inspect)
|
132
150
|
end
|
133
151
|
if @children
|
134
152
|
@children.each do |k, v|
|
135
153
|
io << $/ + indent
|
136
|
-
v.dump_tree(io,
|
154
|
+
v.dump_tree(io, indent)
|
137
155
|
end
|
138
156
|
end
|
139
157
|
end
|
140
158
|
|
141
159
|
private
|
142
160
|
|
143
|
-
def
|
161
|
+
def same_key?(key)
|
162
|
+
@index == key.size and @key.start_with?(key)
|
163
|
+
end
|
164
|
+
|
165
|
+
def collect
|
144
166
|
pool = []
|
145
|
-
each
|
167
|
+
each do |key, value|
|
146
168
|
pool << yield(key, value)
|
147
169
|
end
|
148
170
|
pool
|
149
171
|
end
|
150
172
|
|
151
173
|
def push(key, value)
|
152
|
-
if child = find_child(key)
|
174
|
+
if @children && child = find_child(key[@index])
|
153
175
|
child.store(key, value)
|
154
176
|
else
|
155
|
-
add_child(Node.new(key, value))
|
177
|
+
add_child(Node.new(key, key.size, value))
|
156
178
|
end
|
157
179
|
end
|
158
180
|
|
159
|
-
def split(
|
160
|
-
|
161
|
-
|
162
|
-
@value, @children = UNDEFINED, nil
|
181
|
+
def split(pos)
|
182
|
+
child = Node.new(@key, @index, @value, @children)
|
183
|
+
@index, @value, @children = pos, UNDEFINED, nil
|
163
184
|
add_child(child)
|
164
185
|
end
|
165
186
|
|
@@ -169,38 +190,32 @@ class RadixTree
|
|
169
190
|
elsif child.children.size == 1
|
170
191
|
# pull up the grand child as a child
|
171
192
|
delete_child(child)
|
172
|
-
|
173
|
-
add_child(Node.new(child.key + grand.key, grand.value, grand.children))
|
193
|
+
add_child(child.children.shift[1])
|
174
194
|
end
|
175
195
|
end
|
176
196
|
|
177
|
-
def child_key(key)
|
178
|
-
key[head_match_length(key)..-1]
|
179
|
-
end
|
180
|
-
|
181
|
-
# assert: check != @key
|
182
197
|
def head_match_length(check)
|
183
|
-
0.upto(
|
184
|
-
if check[
|
185
|
-
return
|
198
|
+
0.upto(@index) do |idx|
|
199
|
+
if check[idx] != @key[idx]
|
200
|
+
return idx
|
186
201
|
end
|
187
202
|
end
|
188
|
-
|
203
|
+
@index
|
189
204
|
end
|
190
205
|
|
191
|
-
def find_child(
|
192
|
-
|
193
|
-
@children[key[0]]
|
194
|
-
end
|
206
|
+
def find_child(char)
|
207
|
+
@children[char]
|
195
208
|
end
|
196
209
|
|
197
210
|
def add_child(child)
|
211
|
+
char = child.key[@index]
|
198
212
|
@children ||= {}
|
199
|
-
@children[
|
213
|
+
@children[char] = child
|
200
214
|
end
|
201
215
|
|
202
216
|
def delete_child(child)
|
203
|
-
|
217
|
+
char = child.key[@index]
|
218
|
+
@children.delete(char)
|
204
219
|
if @children.empty?
|
205
220
|
@children = nil
|
206
221
|
end
|
@@ -216,7 +231,7 @@ class RadixTree
|
|
216
231
|
if block && default != DEFAULT
|
217
232
|
raise ArgumentError, 'wrong number of arguments'
|
218
233
|
end
|
219
|
-
@root = Node.new('')
|
234
|
+
@root = Node.new('', 0)
|
220
235
|
@default = default
|
221
236
|
@default_proc = block
|
222
237
|
end
|
@@ -232,58 +247,46 @@ class RadixTree
|
|
232
247
|
|
233
248
|
def each(&block)
|
234
249
|
if block_given?
|
235
|
-
@root.each(
|
250
|
+
@root.each(&block)
|
236
251
|
self
|
237
252
|
else
|
238
|
-
Enumerator.new
|
239
|
-
@root.each('') do |k, v|
|
240
|
-
yielder << [k, v]
|
241
|
-
end
|
242
|
-
}
|
253
|
+
Enumerator.new(@root)
|
243
254
|
end
|
244
255
|
end
|
245
256
|
alias each_pair each
|
246
257
|
|
247
258
|
def each_key
|
248
259
|
if block_given?
|
249
|
-
@root.each
|
260
|
+
@root.each do |k, v|
|
250
261
|
yield k
|
251
262
|
end
|
252
263
|
self
|
253
264
|
else
|
254
|
-
Enumerator.new
|
255
|
-
@root.each('') do |k, v|
|
256
|
-
yielder << k
|
257
|
-
end
|
258
|
-
}
|
265
|
+
Enumerator.new(@root, :each_key)
|
259
266
|
end
|
260
267
|
end
|
261
268
|
|
262
269
|
def each_value
|
263
270
|
if block_given?
|
264
|
-
@root.each
|
271
|
+
@root.each do |k, v|
|
265
272
|
yield v
|
266
273
|
end
|
267
274
|
self
|
268
275
|
else
|
269
|
-
Enumerator.new
|
270
|
-
@root.each('') do |k, v|
|
271
|
-
yielder << v
|
272
|
-
end
|
273
|
-
}
|
276
|
+
Enumerator.new(@root, :each_value)
|
274
277
|
end
|
275
278
|
end
|
276
279
|
|
277
280
|
def keys
|
278
|
-
@root.keys
|
281
|
+
@root.keys
|
279
282
|
end
|
280
283
|
|
281
284
|
def values
|
282
|
-
@root.values
|
285
|
+
@root.values
|
283
286
|
end
|
284
287
|
|
285
288
|
def clear
|
286
|
-
@root = Node.new('')
|
289
|
+
@root = Node.new('', 0)
|
287
290
|
end
|
288
291
|
|
289
292
|
def []=(key, value)
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: radix_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0.dev
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Hiroshi Nakamura
|
@@ -33,18 +33,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
33
33
|
- - ! '>='
|
34
34
|
- !ruby/object:Gem::Version
|
35
35
|
version: '0'
|
36
|
-
segments:
|
37
|
-
- 0
|
38
|
-
hash: -966188071225850011
|
39
36
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
37
|
none: false
|
41
38
|
requirements:
|
42
|
-
- - ! '
|
39
|
+
- - ! '>'
|
43
40
|
- !ruby/object:Gem::Version
|
44
|
-
version:
|
45
|
-
segments:
|
46
|
-
- 0
|
47
|
-
hash: -966188071225850011
|
41
|
+
version: 1.3.1
|
48
42
|
requirements: []
|
49
43
|
rubyforge_project:
|
50
44
|
rubygems_version: 1.8.11
|