radix_tree 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +1 -0
  2. data/lib/radix_tree.rb +216 -14
  3. data/test/test_radix_tree.rb +235 -35
  4. metadata +4 -9
data/README CHANGED
@@ -20,6 +20,7 @@ See comments in lib/radix_tree.rb
20
20
 
21
21
  * 1.0.0 - Initial release.
22
22
  * 1.1.0 - 1.8 support, speed/memory perf improvement.
23
+ * 1.2.0 - Hash methods implemented by Leeheng. Thanks!
23
24
 
24
25
 
25
26
  == Author
@@ -5,23 +5,9 @@
5
5
  # 10 times slower for 10 bytes key, 100000 elements retrieval
6
6
  #
7
7
  # TODO: Implement following methods for Hash compatibility.
8
- # * delete_if
9
- # * reject
10
- # * reject!
11
- # * fetch
12
- # * values_at
13
- # * replace
14
- # * key
15
- # * shift
16
- # * has_value?/value?
17
- # * ==
18
- # * eql?
19
8
  # * hash
20
9
  #
21
10
  # TODO: Implement following features for utilizing strength of Radix Tree.
22
- # * find predecessor
23
- # * find successor
24
- # * find_all by start string
25
11
  # * delete_all by start string
26
12
  #
27
13
  class RadixTree
@@ -155,6 +141,80 @@ class RadixTree
155
141
  end
156
142
  end
157
143
 
144
+ def dup
145
+ if @children
146
+ children_dup = Hash.new
147
+ @children.each do |k,v|
148
+ children_dup[k] = v.dup
149
+ end
150
+ else
151
+ children_dup = nil
152
+ end
153
+ Node.new(@key, @index, @value, children_dup)
154
+ end
155
+ alias clone dup
156
+
157
+ def find_pre(key, head, p_key)
158
+ if same_key?(key)
159
+ (p_key == "" ? nil : p_key)
160
+ else
161
+ if @children
162
+ pos = head_match_size(key, head)
163
+ if child = find_child(key[pos])
164
+ return child.find_pre(key, @index, self.label)
165
+ end
166
+ end
167
+ nil
168
+ end
169
+ end
170
+
171
+ def find_suc(key, head, flag)
172
+ # check before the key or after
173
+ flag = true if !flag && same_key?(key)
174
+ if flag
175
+ # after tje key and omit undefined value
176
+ if undefined? || self.label == key
177
+ # if undefined keep going
178
+ if @children
179
+ # next by lexicographic order
180
+ k = @children.keys.sort.shift
181
+ return @children[k].find_suc(key, nil, flag)
182
+ end
183
+ nil
184
+ else
185
+ return self.label
186
+ end
187
+ else
188
+ # before the key
189
+ if @children
190
+ pos = head_match_size(key, head)
191
+ if child = find_child(key[pos])
192
+ return child.find_suc(key, @index, flag)
193
+ end
194
+ end
195
+ nil
196
+ end
197
+ end
198
+
199
+ def find_all(prefix, head, flag)
200
+ flag = true if !flag && same_key?(prefix)
201
+ if flag
202
+ strs = []
203
+ self.each do |l,v|
204
+ strs << l if v != UNDEFINED
205
+ end
206
+ return strs
207
+ else
208
+ if @children
209
+ pos = head_match_size(prefix, head)
210
+ if child = find_child(prefix[pos])
211
+ return child.find_all(prefix, @index, flag)
212
+ end
213
+ end
214
+ nil
215
+ end
216
+ end
217
+
158
218
  private
159
219
 
160
220
  def same_key?(key)
@@ -325,4 +385,146 @@ class RadixTree
325
385
  def to_hash
326
386
  inject({}) { |r, (k, v)| r[k] = v; r }
327
387
  end
388
+
389
+ def delete_if(&block)
390
+ if block_given?
391
+ temp = []
392
+ @root.each do |key, value|
393
+ if block.call key, value
394
+ temp << key
395
+ end
396
+ end
397
+ temp.each do |k|
398
+ @root.delete(k, 0)
399
+ end
400
+ self
401
+ else
402
+ Enumerator.new(@root)
403
+ end
404
+ end
405
+
406
+ def dup
407
+ if @default != DEFAULT then
408
+ rt = RadixTree.new(@default)
409
+ elsif @default_proc then
410
+ rt = RadixTree.new(@default_proc.to_proc)
411
+ else
412
+ rt = RadixTree.new
413
+ end
414
+ rt.root = @root.dup
415
+ rt
416
+ end
417
+ alias clone dup
418
+
419
+ def reject(&block)
420
+ if block_given?
421
+ self.dup.delete_if(&block)
422
+ else
423
+ Enumerator.new(@root)
424
+ end
425
+ end
426
+
427
+ def reject!(&block)
428
+ if block_given?
429
+ temp = []
430
+ @root.each do |key, value|
431
+ if block.call key, value
432
+ temp << key
433
+ end
434
+ end
435
+ if temp.empty?
436
+ nil
437
+ else
438
+ temp.each do |k|
439
+ @root.delete(k, 0)
440
+ end
441
+ self
442
+ end
443
+ else
444
+ Enumerator.new(@root)
445
+ end
446
+ end
447
+
448
+ def fetch(key, *args, &block)
449
+ if args.size > 0 && block
450
+ raise ArgumentError, 'wrong number of arguments'
451
+ elsif self[key]
452
+ self[key]
453
+ elsif args.size == 1
454
+ args[0]
455
+ elsif block
456
+ block.call key
457
+ else
458
+ raise KeyError, 'can\'t find the key'
459
+ end
460
+ end
461
+
462
+ def values_at(*args)
463
+ vs = []
464
+ args.each do |a|
465
+ vs << self[a]
466
+ end
467
+ vs
468
+ end
469
+
470
+ def replace(h)
471
+ self.clear
472
+ h.each do |k,v|
473
+ self[k] = v
474
+ end
475
+ end
476
+
477
+ def key(value)
478
+ self.each do |k,v|
479
+ return k if v == value
480
+ nil
481
+ end
482
+ end
483
+
484
+ def shift
485
+ self.each do |k,v|
486
+ self.delete(k)
487
+ return [k, v]
488
+ end
489
+ end
490
+
491
+ def has_value?(value)
492
+ self.each do |k,v|
493
+ return true if value == v
494
+ end
495
+ false
496
+ end
497
+ alias value? has_value?
498
+
499
+ def ==(oh)
500
+ return false if self.size != oh.size
501
+ self.each_key do |k|
502
+ return false if self[k] != oh[k]
503
+ end
504
+ true
505
+ end
506
+
507
+ def eql?(oh)
508
+ return false if self.size != oh.size
509
+ self.each_key do |k|
510
+ return false unless self[k].eql?(oh[k])
511
+ end
512
+ true
513
+ end
514
+
515
+ def find_predecessor(key)
516
+ @root.find_pre(key, 0, nil)
517
+ end
518
+
519
+ def find_successor(key)
520
+ @root.find_suc(key, 0, false)
521
+ end
522
+
523
+ def find_all(prefix)
524
+ @root.find_all(prefix, 0, false)
525
+ end
526
+
527
+ protected
528
+ attr_accessor :root
529
+
328
530
  end
@@ -2,6 +2,16 @@
2
2
  require File.expand_path('./helper', File.dirname(__FILE__))
3
3
 
4
4
  class TestRadixTree < Test::Unit::TestCase
5
+ def setup
6
+ # rt for RadixTree
7
+ # ip for input
8
+ @rt = RadixTree.new
9
+ @ip = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
10
+ @ip.each do |k, v|
11
+ @rt[k] = v
12
+ end
13
+ end
14
+
5
15
  def test_aref_nil
6
16
  h = RadixTree.new
7
17
  h['abc'] = 1
@@ -176,26 +186,18 @@ class TestRadixTree < Test::Unit::TestCase
176
186
  end
177
187
 
178
188
  def test_each
179
- h = RadixTree.new
180
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
181
- s.each do |k, v|
182
- h[k] = v
183
- end
189
+ h, s = @rt, @ip
184
190
  assert_equal s.to_a.sort_by { |k, v| k }, h.each.sort_by { |k, v| k }
185
191
  #
186
192
  values = []
187
193
  h.each do |k, v|
188
194
  values << [k, v]
189
195
  end
190
- assert_equal s.to_a.sort_by { |k, v| k }, values.sort_by { |k, v| k }
196
+ assert_equal h.to_a.sort_by { |k, v| k }, values.sort_by { |k, v| k }
191
197
  end
192
198
 
193
199
  def test_each_key
194
- h = RadixTree.new
195
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
196
- s.each do |k, v|
197
- h[k] = v
198
- end
200
+ h, s = @rt, @ip
199
201
  assert_equal s.keys.sort, h.each_key.sort
200
202
  #
201
203
  values = []
@@ -206,35 +208,23 @@ class TestRadixTree < Test::Unit::TestCase
206
208
  end
207
209
 
208
210
  def test_each_value
209
- h = RadixTree.new
210
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6, 'azzzzz' => 6 }
211
- s.each do |k, v|
212
- h[k] = v
213
- end
211
+ h, s = @rt, @ip
214
212
  assert_equal s.values.sort, h.each_value.sort
215
213
  #
216
214
  values = []
217
215
  h.each_value do |v|
218
216
  values << v
219
217
  end
220
- assert_equal s.values.sort, values.sort
218
+ assert_equal h.values.sort, values.sort
221
219
  end
222
220
 
223
221
  def test_keys
224
- h = RadixTree.new
225
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
226
- s.each do |k, v|
227
- h[k] = v
228
- end
222
+ h, s = @rt, @ip
229
223
  assert_equal s.keys.sort, h.keys.sort
230
224
  end
231
225
 
232
226
  def test_values
233
- h = RadixTree.new
234
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
235
- s.each do |k, v|
236
- h[k] = v
237
- end
227
+ h, s = @rt, @ip
238
228
  assert_equal s.values.sort, h.values.sort
239
229
  end
240
230
 
@@ -272,24 +262,234 @@ class TestRadixTree < Test::Unit::TestCase
272
262
  end
273
263
 
274
264
  def test_to_hash
265
+ h, s = @rt, @ip
266
+ assert_equal s, h.to_hash
267
+ end
268
+
269
+ def test_clear
270
+ assert_equal @ip, @rt.to_hash
271
+ @rt.clear
272
+ assert_equal 0, @rt.size
273
+ assert @rt.to_hash.empty?
274
+ end
275
+
276
+ def test_delete_if
277
+ h, s = @rt, @ip
278
+ assert_equal 6, h.size
279
+ h.delete_if do |k,v|
280
+ v > 3
281
+ end
282
+ assert_equal 3, h.size
283
+ assert_equal 1, h['aa']
284
+ assert_equal 2, h['ab']
285
+ assert_equal 3, h['bb']
286
+ assert_equal nil, h['bc']
287
+ assert_equal nil, h['a']
288
+ assert_equal nil, h['abc']
289
+ end
290
+
291
+ def test_dup
275
292
  h = RadixTree.new
276
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
293
+ s = { 'aa' => 1, 'ab' => 2, 'bb' => 3 }
277
294
  s.each do |k, v|
278
295
  h[k] = v
279
296
  end
280
- assert_equal s, h.to_hash
297
+ assert_equal 3, h.size
298
+ assert_equal 1, h['aa']
299
+ assert_equal 2, h['ab']
300
+ assert_equal 3, h['bb']
301
+ h2 = h.dup
302
+ h2['aa'] = 4
303
+ h2['a'] = 5
304
+
305
+ assert_equal 3, h.size
306
+ assert_equal 1, h['aa']
307
+ assert_equal 2, h['ab']
308
+ assert_equal 3, h['bb']
309
+ assert_equal 4, h2.size
310
+ assert_equal 4, h2['aa']
311
+ assert_equal 2, h2['ab']
312
+ assert_equal 3, h2['bb']
313
+ assert_equal 5, h2['a']
281
314
  end
282
315
 
283
- def test_clear
316
+ def test_reject
317
+ h, s = @rt, @ip
318
+ h2 = h.reject do |k,v|
319
+ v > 3
320
+ end
321
+ assert_equal 6, h.size
322
+ assert_equal 1, h['aa']
323
+ assert_equal 2, h['ab']
324
+ assert_equal 3, h['bb']
325
+ assert_equal 4, h['bc']
326
+ assert_equal 5, h['a']
327
+ assert_equal 6, h['abc']
328
+
329
+ assert_equal 3, h2.size
330
+ assert_equal 1, h2['aa']
331
+ assert_equal 2, h2['ab']
332
+ assert_equal 3, h2['bb']
333
+ assert_equal nil, h2['bc']
334
+ assert_equal nil, h2['a']
335
+ assert_equal nil, h2['abc']
336
+ end
337
+
338
+ def test_reject!
339
+ h, s = @rt, @ip
340
+ h2 = h.reject! do |k,v|
341
+ v > 8
342
+ end
343
+ assert_equal 6, h.size
344
+ assert_equal 1, h['aa']
345
+ assert_equal 2, h['ab']
346
+ assert_equal 3, h['bb']
347
+ assert_equal 4, h['bc']
348
+ assert_equal 5, h['a']
349
+ assert_equal 6, h['abc']
350
+
351
+ assert_equal nil, h2
352
+ end
353
+
354
+ def fetch!
284
355
  h = RadixTree.new
285
- s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 }
356
+ s = { 'aa' => 1, 'ab' => 2 }
286
357
  s.each do |k, v|
287
358
  h[k] = v
288
359
  end
289
- assert_equal s, h.to_hash
290
- h.clear
291
- assert_equal 0, h.size
292
- assert h.to_hash.empty?
360
+ assert_equal 1, h.fetch('aa')
361
+ assert_equal 'df value', h.fetch('aac', 'df value')
362
+ assert_equal "aac:df value from block", h.fetch('aac') {|k| "#{k}:df value from block" }
363
+ end
364
+
365
+ def test_values_at
366
+ h, s = @rt, @ip
367
+ ks = s.keys.shuffle[0..2]
368
+ vs = h.values_at(*ks)
369
+ for i in (0..(ks.size)) do
370
+ assert_equal vs[i], h[ks[i]]
371
+ end
372
+ end
373
+
374
+ def test_replace
375
+ h = RadixTree.new
376
+ s = { 'aa' => 1, 'ab' => 2 }
377
+ s2 = { 'bz' => 3, 'kk' => 4 }
378
+ s.each do |k, v|
379
+ h[k] = v
380
+ end
381
+ assert_equal 1, h['aa']
382
+ assert_equal 2, h['ab']
383
+ h.replace(s2)
384
+ assert_equal 3, h['bz']
385
+ assert_equal 4, h['kk']
386
+ end
387
+
388
+ def test_key
389
+ h, s = @rt, @ip
390
+ assert_equal 'aa', h.key(1)
391
+ assert_equal 'bb', h.key(3)
392
+ assert_equal 'bc', h.key(4)
393
+ end
394
+
395
+ def test_shift
396
+ h, s = @rt, @ip
397
+ k, v = h.shift
398
+ assert_equal 'a', k
399
+ assert_equal 5, v
400
+ assert_equal 1, h['aa']
401
+ assert_equal 2, h['ab']
402
+ assert_equal 3, h['bb']
403
+ assert_equal 4, h['bc']
404
+ assert_equal 6, h['abc']
405
+ end
406
+
407
+ def test_has_value?
408
+ h, s = @rt, @ip
409
+ assert_equal true, h.has_value?(3)
410
+ assert_equal true, h.has_value?(4)
411
+ assert_equal true, h.value?(5)
412
+ assert_equal true, h.value?(6)
413
+ assert_equal false, h.has_value?(7)
414
+ end
415
+
416
+ def test_=
417
+ h, s = @rt, @ip
418
+ h2 = RadixTree.new
419
+ s.each do |k, v|
420
+ h2[k] = v
421
+ end
422
+ assert_equal true, (h==h2)
423
+ tk, tv= h2.shift
424
+ assert_equal false, (h==h2)
425
+ h2[tk] = tv+3
426
+ assert_equal false, (h==h2)
427
+ end
428
+
429
+ def test_eql?
430
+ h, s = @rt, @ip
431
+ h2 = RadixTree.new
432
+ s.each do |k, v|
433
+ h2[k] = v
434
+ end
435
+ assert_equal true, (h.eql?(h2))
436
+ tk, tv= h2.shift
437
+ assert_equal false, (h.eql?(h2))
438
+ h2[tk] = tv.to_s
439
+ assert_equal false, (h.eql?(h2))
440
+ end
441
+
442
+ def test_find_predecessor
443
+ h, s = @rt, @ip
444
+ assert_equal nil, h.find_predecessor('a')
445
+ assert_equal nil, h.find_predecessor('b')
446
+ assert_equal 'a', h.find_predecessor('aa')
447
+ assert_equal 'a', h.find_predecessor('ab')
448
+ assert_equal 'ab', h.find_predecessor('abc')
449
+ assert_equal 'b', h.find_predecessor('bb')
450
+ assert_equal 'b', h.find_predecessor('bc')
451
+ end
452
+
453
+ def test_find_successor
454
+ h, s = @rt, @ip
455
+ h['cde'] = 7
456
+ h['cdf'] = 8
457
+ h['cf'] = 9
458
+ h['c'] = 10
459
+ assert_equal 'aa', h.find_successor('a')
460
+ assert_equal nil, h.find_successor('aa')
461
+ assert_equal 'abc', h.find_successor('ab')
462
+ assert_equal nil, h.find_successor('abc')
463
+ assert_equal 'bb', h.find_successor('b')
464
+ assert_equal nil, h.find_successor('bb')
465
+ assert_equal nil, h.find_successor('bc')
466
+ assert_equal 'cde', h.find_successor('c')
467
+ assert_equal 'cde', h.find_successor('cd')
468
+ assert_equal nil, h.find_successor('cde')
469
+ assert_equal nil, h.find_successor('cdf')
470
+ assert_equal nil, h.find_successor('cf')
471
+ end
472
+
473
+ def test_find_all
474
+ h, s = @rt, @ip
475
+ h['cde'] = 7
476
+ h['cdf'] = 8
477
+ h['cf'] = 9
478
+ h['c'] = 10
479
+ assert_equal ['ab', 'abc'].sort, h.find_all('ab').sort
480
+ assert_equal ['a', 'aa', 'ab', 'abc'].sort, h.find_all('a').sort
481
+ assert_equal ['aa'], h.find_all('aa')
482
+ assert_equal ['abc'], h.find_all('abc')
483
+
484
+ assert_equal ['bb', 'bc'].sort, h.find_all('b').sort
485
+ assert_equal ['bb'], h.find_all('bb')
486
+ assert_equal ['bc'], h.find_all('bc')
487
+
488
+ assert_equal ['c', 'cde', 'cdf', 'cf'].sort, h.find_all('c').sort
489
+ assert_equal ['cde', 'cdf'].sort, h.find_all('cd').sort
490
+ assert_equal ['cde'], h.find_all('cde')
491
+ assert_equal ['cdf'], h.find_all('cdf')
492
+ assert_equal ['cf'], h.find_all('cf')
293
493
  end
294
494
 
295
495
  if RUBY_VERSION >= '1.9.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radix_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-09 00:00:00.000000000 Z
12
+ date: 2012-10-29 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: nahi@ruby-lang.org
@@ -33,22 +33,17 @@ 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: 2336052632168712106
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
41
  version: '0'
45
- segments:
46
- - 0
47
- hash: 2336052632168712106
48
42
  requirements: []
49
43
  rubyforge_project:
50
- rubygems_version: 1.8.11
44
+ rubygems_version: 1.8.23
51
45
  signing_key:
52
46
  specification_version: 3
53
47
  summary: Naive implementation of Radix Tree for Ruby
54
48
  test_files: []
49
+ has_rdoc: