radix_tree 1.0.0 → 1.1.0.dev
Sign up to get free protection for your applications and to get access to all the features.
- 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
|