avl_tree 1.1.3 → 1.2.0

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/bench/bench_thread.rb +39 -0
  4. data/lib/avl_tree.rb +4 -0
  5. data/lib/red_black_tree.rb +334 -38
  6. data/test/coverage/assets/0.8.0/application.css +799 -0
  7. data/test/coverage/assets/0.8.0/application.js +1559 -0
  8. data/test/coverage/assets/0.8.0/colorbox/border.png +0 -0
  9. data/test/coverage/assets/0.8.0/colorbox/controls.png +0 -0
  10. data/test/coverage/assets/0.8.0/colorbox/loading.gif +0 -0
  11. data/test/coverage/assets/0.8.0/colorbox/loading_background.png +0 -0
  12. data/test/coverage/assets/0.8.0/favicon_green.png +0 -0
  13. data/test/coverage/assets/0.8.0/favicon_red.png +0 -0
  14. data/test/coverage/assets/0.8.0/favicon_yellow.png +0 -0
  15. data/test/coverage/assets/0.8.0/loading.gif +0 -0
  16. data/test/coverage/assets/0.8.0/magnify.png +0 -0
  17. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  18. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  19. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  20. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  21. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  22. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  23. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  24. data/test/coverage/assets/0.8.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  25. data/test/coverage/assets/0.8.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  26. data/test/coverage/assets/0.8.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  27. data/test/coverage/assets/0.8.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  28. data/test/coverage/assets/0.8.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  29. data/test/coverage/assets/0.8.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  30. data/test/coverage/index.html +72 -0
  31. data/test/test_avl_tree.rb +37 -0
  32. data/test/test_red_black_tree.rb +61 -45
  33. data/test/test_red_black_tree_thread.rb +39 -0
  34. metadata +40 -21
  35. data/README +0 -16
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 88542c761338424b654e97c1966f46a7d9fa2a93
4
+ data.tar.gz: 1cdba53af91bb3d3fea623c003011ad476ed7a7b
5
+ SHA512:
6
+ metadata.gz: 7f2cd6ca32b986105d3d004130e71eec83ef8c2ca8eda35201f6e944406f49b08c61dd32c73a283295e0d30709be51af130f20da5b53be007fd3cdf5f3a70c75
7
+ data.tar.gz: 3f25b697bee9033232507f7df293f9f311a761cf2c6b09d5f38493eb5666d6faa415e8a829b3051bd2f87af31503583cfe8ebb63bd5aac04da376f9ed6cc1eba
@@ -0,0 +1,42 @@
1
+ # AVL tree, Red-black tree in Ruby
2
+
3
+ avl_tree - AVL tree, Red-black tree and Lock-free Red black tree in Ruby
4
+ Copyright (C) 2014 Hiroshi Nakamura <nahi@ruby-lang.org>
5
+
6
+
7
+ ## Usage
8
+
9
+ You can use AVLTree, RedBlackTree or ConcurrentRedBlackTree just as a
10
+ replacement of Hash.
11
+
12
+ @points = Hash.new
13
+ ...
14
+ @points[score] = person
15
+ ...
16
+ @points.each do |score, person|
17
+ ...
18
+ end
19
+
20
+ ->
21
+
22
+ require 'avl_tree'
23
+ @points = AVLTree.new
24
+
25
+ require 'red_black_tree'
26
+ @points = RedBlackTree.new
27
+ @points = ConcurrentRedBlackTree.new
28
+
29
+ AVLTree and RedBlackTree are faster but not thread-safe. Use ConcurrentRedBlackTree in multi-thread environment.
30
+
31
+ ## Author
32
+
33
+ Name:: Hiroshi Nakamura
34
+ E-mail:: nahi@ruby-lang.org
35
+ Project web site:: http://github.com/nahi/avl_tree
36
+
37
+
38
+ ## License
39
+
40
+ This program is copyrighted free software by Hiroshi Nakamura. You can
41
+ redistribute it and/or modify it under the same terms of Ruby's license;
42
+ either the dual license version in 2003, or any later version.
@@ -0,0 +1,39 @@
1
+ require 'benchmark'
2
+ require 'red_black_tree'
3
+
4
+ Benchmark.bmbm do |bm|
5
+ bm.report do
6
+ h = ConcurrentRedBlackTree.new
7
+ num = 100000
8
+ max = 1000
9
+ threads = []
10
+ # writers
11
+ 2.times do
12
+ threads << Thread.new {
13
+ num.times do
14
+ key = rand(max)
15
+ h[key] = key
16
+ end
17
+ }
18
+ end
19
+ # deleters
20
+ 2.times do
21
+ threads << Thread.new {
22
+ num.times do
23
+ key = rand(max)
24
+ h.delete(key)
25
+ end
26
+ }
27
+ end
28
+ # readers
29
+ 2.times do
30
+ threads << Thread.new {
31
+ num.times do
32
+ key = rand(max)
33
+ h[key]
34
+ end
35
+ }
36
+ end
37
+ threads.each(&:join)
38
+ end
39
+ end
@@ -314,6 +314,10 @@ class AVLTree
314
314
  end
315
315
  alias length size
316
316
 
317
+ def height
318
+ @root.height
319
+ end
320
+
317
321
  def each(&block)
318
322
  if block_given?
319
323
  @root.each(&block)
@@ -1,3 +1,5 @@
1
+ require 'atomic'
2
+
1
3
  class RedBlackTree
2
4
  include Enumerable
3
5
 
@@ -7,11 +9,13 @@ class RedBlackTree
7
9
  attr_reader :key, :value, :color
8
10
  attr_reader :left, :right
9
11
 
10
- def initialize(key, value)
11
- @key, @value = key, value
12
- @left = @right = EMPTY
12
+ def initialize(key, value, left, right, color = :RED)
13
+ @key = key
14
+ @value = value
15
+ @left = left
16
+ @right = right
13
17
  # new node is added as RED
14
- @color = :RED
18
+ @color = color
15
19
  end
16
20
 
17
21
  def set_root
@@ -67,14 +71,14 @@ class RedBlackTree
67
71
  case key <=> @key
68
72
  when -1
69
73
  @left = @left.insert(key, value)
70
- if black? and @right.black? and @left.red? and !@left.children_both_black?
74
+ if black? and @right.black? and @left.red? and !@left.children_color?(:BLACK)
71
75
  ret = rebalance_for_left_insert
72
76
  end
73
77
  when 0
74
78
  @value = value
75
79
  when 1
76
80
  @right = @right.insert(key, value)
77
- if black? and @left.black? and @right.red? and !@right.children_both_black?
81
+ if black? and @left.black? and @right.red? and !@right.children_color?(:BLACK)
78
82
  ret = rebalance_for_right_insert
79
83
  end
80
84
  else
@@ -108,7 +112,7 @@ class RedBlackTree
108
112
  end
109
113
  when 0
110
114
  deleted = self
111
- ret, rebalance = delete_self
115
+ ret, rebalance = delete_node
112
116
  when 1
113
117
  deleted, @right, rebalance = @right.delete(key)
114
118
  if rebalance
@@ -156,8 +160,8 @@ class RedBlackTree
156
160
 
157
161
  protected
158
162
 
159
- def children_both_black?
160
- @right.black? and @left.black?
163
+ def children_color?(color)
164
+ @right.color == @left.color && @right.color == color
161
165
  end
162
166
 
163
167
  def color=(color)
@@ -176,15 +180,9 @@ class RedBlackTree
176
180
  @color, other.color = other.color, @color
177
181
  end
178
182
 
179
- def node_flip(other)
180
- @left, other.left = other.left, @left
181
- @right, other.right = other.right, @right
182
- color_flip(other)
183
- end
184
-
185
183
  def delete_min
186
184
  if @left.empty?
187
- [self, *delete_self]
185
+ [self, *delete_node]
188
186
  else
189
187
  ret = self
190
188
  deleted, @left, rebalance = @left.delete_min
@@ -201,7 +199,7 @@ class RedBlackTree
201
199
  rebalance = false
202
200
  if black?
203
201
  if @right.black?
204
- if @right.children_both_black?
202
+ if @right.children_color?(:BLACK)
205
203
  # make whole sub-tree 1 level lower and ask rebalance
206
204
  @right.color = :RED
207
205
  rebalance = true
@@ -217,7 +215,7 @@ class RedBlackTree
217
215
  raise 'should not happen' if rebalance
218
216
  end
219
217
  else # red
220
- if @right.children_both_black?
218
+ if @right.children_color?(:BLACK)
221
219
  # make right sub-tree 1 level lower
222
220
  color_flip(@right)
223
221
  else
@@ -235,7 +233,7 @@ class RedBlackTree
235
233
  rebalance = false
236
234
  if black?
237
235
  if @left.black?
238
- if @left.children_both_black?
236
+ if @left.children_color?(:BLACK)
239
237
  @left.color = :RED
240
238
  rebalance = true
241
239
  else
@@ -247,7 +245,7 @@ class RedBlackTree
247
245
  raise 'should not happen' if rebalance
248
246
  end
249
247
  else # red
250
- if @left.children_both_black?
248
+ if @left.children_color?(:BLACK)
251
249
  color_flip(@left)
252
250
  else
253
251
  ret = balanced_rotate_right
@@ -318,7 +316,7 @@ class RedBlackTree
318
316
  # A C a c
319
317
  #
320
318
  def pullup_red
321
- if black? and @left.red? and @right.red?
319
+ if black? and children_color?(:RED)
322
320
  @left.color = @right.color = :BLACK
323
321
  self.color = :RED
324
322
  end
@@ -346,7 +344,7 @@ class RedBlackTree
346
344
  rotate_left
347
345
  end
348
346
 
349
- def delete_self
347
+ def delete_node
350
348
  rebalance = false
351
349
  if @left.empty? and @right.empty?
352
350
  # just remove this node and ask rebalance to the parent
@@ -366,8 +364,8 @@ class RedBlackTree
366
364
  end
367
365
  else
368
366
  # pick the minimum node from the right sub-tree and replace self with it
369
- new_root, @right, rebalance = @right.delete_min
370
- new_root.node_flip(self)
367
+ deleted, @right, rebalance = @right.delete_min
368
+ new_root = Node.new(deleted.key, deleted.value, @left, @right, @color)
371
369
  if rebalance
372
370
  new_root, rebalance = new_root.rebalance_for_right_delete
373
371
  end
@@ -403,7 +401,7 @@ class RedBlackTree
403
401
 
404
402
  # returns new_root
405
403
  def insert(key, value)
406
- Node.new(key, value)
404
+ Node.new(key, value, self, self)
407
405
  end
408
406
 
409
407
  # returns value
@@ -441,53 +439,57 @@ class RedBlackTree
441
439
  @default_proc = block
442
440
  end
443
441
 
442
+ def root
443
+ @root
444
+ end
445
+
444
446
  def empty?
445
- @root == Node::EMPTY
447
+ root == Node::EMPTY
446
448
  end
447
449
 
448
450
  def size
449
- @root.size
451
+ root.size
450
452
  end
451
453
  alias length size
452
454
 
453
455
  def each(&block)
454
456
  if block_given?
455
- @root.each(&block)
457
+ root.each(&block)
456
458
  self
457
459
  else
458
- Enumerator.new(@root)
460
+ Enumerator.new(root)
459
461
  end
460
462
  end
461
463
  alias each_pair each
462
464
 
463
465
  def each_key
464
466
  if block_given?
465
- @root.each do |k, v|
467
+ root.each do |k, v|
466
468
  yield k
467
469
  end
468
470
  self
469
471
  else
470
- Enumerator.new(@root, :each_key)
472
+ Enumerator.new(root, :each_key)
471
473
  end
472
474
  end
473
475
 
474
476
  def each_value
475
477
  if block_given?
476
- @root.each do |k, v|
478
+ root.each do |k, v|
477
479
  yield v
478
480
  end
479
481
  self
480
482
  else
481
- Enumerator.new(@root, :each_value)
483
+ Enumerator.new(root, :each_value)
482
484
  end
483
485
  end
484
486
 
485
487
  def keys
486
- @root.keys
488
+ root.keys
487
489
  end
488
490
 
489
491
  def values
490
- @root.values
492
+ root.values
491
493
  end
492
494
 
493
495
  def clear
@@ -502,7 +504,7 @@ class RedBlackTree
502
504
  alias insert []=
503
505
 
504
506
  def key?(key)
505
- @root.retrieve(key) != Node::UNDEFINED
507
+ root.retrieve(key) != Node::UNDEFINED
506
508
  end
507
509
  alias has_key? key?
508
510
 
@@ -525,13 +527,13 @@ class RedBlackTree
525
527
  end
526
528
 
527
529
  def dump_tree(io = '')
528
- @root.dump_tree(io)
530
+ root.dump_tree(io)
529
531
  io << $/
530
532
  io
531
533
  end
532
534
 
533
535
  def dump_sexp
534
- @root.dump_sexp || ''
536
+ root.dump_sexp || ''
535
537
  end
536
538
 
537
539
  def to_hash
@@ -550,3 +552,297 @@ private
550
552
  end
551
553
  end
552
554
  end
555
+
556
+ class ConcurrentRedBlackTree < RedBlackTree
557
+ class ConcurrentNode < Node
558
+ # direction: ~LEFT == RIGHT, ~RIGHT == LEFT
559
+ LEFT = -1
560
+ RIGHT = 0
561
+
562
+ # @Overrides
563
+ def insert(key, value)
564
+ case key <=> @key
565
+ when -1
566
+ dir = LEFT
567
+ when 0
568
+ node = new_value(value)
569
+ when 1
570
+ dir = RIGHT
571
+ else
572
+ raise TypeError, "cannot compare #{key} and #{@key} with <=>"
573
+ end
574
+ if dir
575
+ target = child(dir).insert(key, value)
576
+ node = new_child(dir, target)
577
+ if black? and child(~dir).black? and target.red? and !target.children_color?(:BLACK)
578
+ node = node.rebalance_for_insert(dir)
579
+ end
580
+ end
581
+ node.pullup_red
582
+ end
583
+
584
+ # @Overrides
585
+ def retrieve(key)
586
+ case key <=> @key
587
+ when -1
588
+ @left.retrieve(key)
589
+ when 0
590
+ @value
591
+ when 1
592
+ @right.retrieve(key)
593
+ else
594
+ nil
595
+ end
596
+ end
597
+
598
+ # @Overrides
599
+ def delete(key)
600
+ case key <=> @key
601
+ when -1
602
+ dir = LEFT
603
+ when 0
604
+ deleted = self
605
+ node, rebalance = delete_node
606
+ when 1
607
+ dir = RIGHT
608
+ else
609
+ raise TypeError, "cannot compare #{key} and #{@key} with <=>"
610
+ end
611
+ if dir
612
+ deleted, target, rebalance = child(dir).delete(key)
613
+ node = new_child(dir, target)
614
+ if rebalance
615
+ node, rebalance = node.rebalance_for_delete(dir)
616
+ end
617
+ end
618
+ [deleted, node, rebalance]
619
+ end
620
+
621
+ protected
622
+
623
+ def new_children(dir, node, other, color = @color)
624
+ dir == LEFT ?
625
+ ConcurrentNode.new(@key, @value, node, other, color) :
626
+ ConcurrentNode.new(@key, @value, other, node, color)
627
+ end
628
+
629
+ def new_child(dir, node, color = @color)
630
+ dir == LEFT ?
631
+ ConcurrentNode.new(@key, @value, node, @right, color) :
632
+ ConcurrentNode.new(@key, @value, @left, node, color)
633
+ end
634
+
635
+ def new_color(color)
636
+ ConcurrentNode.new(@key, @value, @left, @right, color)
637
+ end
638
+
639
+ def new_value(value)
640
+ ConcurrentNode.new(@key, value, @left, @right, @color)
641
+ end
642
+
643
+ def child(dir)
644
+ dir == LEFT ? @left : @right
645
+ end
646
+
647
+ # @Overrides
648
+ def delete_min
649
+ if @left.empty?
650
+ [self, *delete_node]
651
+ else
652
+ deleted, left, rebalance = @left.delete_min
653
+ node = new_child(LEFT, left)
654
+ if rebalance
655
+ node, rebalance = node.rebalance_for_delete(LEFT)
656
+ end
657
+ [deleted, node, rebalance]
658
+ end
659
+ end
660
+
661
+ # rebalance when the left/right sub-tree is 1 level lower than the right/left
662
+ def rebalance_for_delete(dir)
663
+ target = child(~dir)
664
+ rebalance = false
665
+ if black?
666
+ if target.black?
667
+ if target.children_color?(:BLACK)
668
+ # make whole sub-tree 1 level lower and ask rebalance
669
+ node = new_child(~dir, target.new_color(:RED))
670
+ rebalance = true
671
+ else
672
+ # move 1 black from the right to the left by single/double rotation
673
+ node = balanced_rotate(dir)
674
+ end
675
+ else
676
+ # flip this sub-tree into another type of 3-children node
677
+ node = rotate(dir)
678
+ # try to rebalance in sub-tree
679
+ target, rebalance = node.child(dir).rebalance_for_delete(dir)
680
+ raise 'should not happen' if rebalance
681
+ node = node.new_children(dir, target, node.child(~dir))
682
+ end
683
+ else # red
684
+ if target.children_color?(:BLACK)
685
+ # make right sub-tree 1 level lower
686
+ node = new_child(~dir, target.new_color(@color), target.color)
687
+ else
688
+ # move 1 black from the right to the left by single/double rotation
689
+ node = balanced_rotate(dir)
690
+ end
691
+ end
692
+ [node, rebalance]
693
+ end
694
+
695
+ # move 1 black from the right/left to the left/right by single/double rotation
696
+ def balanced_rotate(dir)
697
+ target = child(~dir)
698
+ if target.child(dir).red? and target.child(~dir).black?
699
+ node = new_child(~dir, target.rotate(~dir))
700
+ else
701
+ node = self
702
+ end
703
+ node = node.rotate(dir)
704
+ node.new_children(dir, node.child(dir).new_color(:BLACK), node.child(~dir).new_color(:BLACK))
705
+ end
706
+
707
+ # Right single rotation
708
+ # (b a (D c E)) where D and E are RED --> (d (B a c) E)
709
+ #
710
+ # b d
711
+ # / \ / \
712
+ # a D -> B E
713
+ # / \ / \
714
+ # c E a c
715
+ #
716
+ # Left single rotation
717
+ # (d (B A c) e) where A and B are RED --> (b A (D c e))
718
+ #
719
+ # d b
720
+ # / \ / \
721
+ # B e -> A D
722
+ # / \ / \
723
+ # A c c e
724
+ #
725
+ def rotate(dir)
726
+ new_root = child(~dir)
727
+ node = new_child(~dir, new_root.child(dir), new_root.color)
728
+ new_root.new_children(dir, node, new_root.child(~dir), @color)
729
+ end
730
+
731
+ # Pull up red nodes
732
+ # (b (A C)) where A and C are RED --> (B (a c))
733
+ #
734
+ # b B
735
+ # / \ -> / \
736
+ # A C a c
737
+ #
738
+ # @Overrides
739
+ def pullup_red
740
+ if black? and @left.red? and @right.red?
741
+ new_children(LEFT, @left.new_color(:BLACK), @right.new_color(:BLACK), :RED)
742
+ else
743
+ self
744
+ end
745
+ end
746
+
747
+ # rebalance when the left/right sub-tree is 1 level higher than the right/left
748
+ # move 1 black from the left to the right by single/double rotation
749
+ #
750
+ # precondition: self is black and @left/@right is red
751
+ def rebalance_for_insert(dir)
752
+ node = self
753
+ if child(dir).child(~dir).red?
754
+ node = new_child(dir, child(dir).rotate(dir))
755
+ end
756
+ node.rotate(~dir)
757
+ end
758
+
759
+ private
760
+
761
+ # @Overrides
762
+ def delete_node
763
+ rebalance = false
764
+ if @left.empty? and @right.empty?
765
+ # just remove this node and ask rebalance to the parent
766
+ new_node = EMPTY_CONCURRENT
767
+ if black?
768
+ rebalance = true
769
+ end
770
+ elsif @left.empty? or @right.empty?
771
+ # pick the single children
772
+ new_node = @left.empty? ? @right : @left
773
+ if black?
774
+ # keep the color black
775
+ raise 'should not happen' unless new_node.red?
776
+ new_node = new_node.new_color(@color)
777
+ else
778
+ # just remove the red node
779
+ end
780
+ else
781
+ # pick the minimum node from the right sub-tree and replace self with it
782
+ deleted, right, rebalance = @right.delete_min
783
+ new_node = deleted.new_children(LEFT, @left, right, @color)
784
+ if rebalance
785
+ new_node, rebalance = new_node.rebalance_for_delete(RIGHT)
786
+ end
787
+ end
788
+ [new_node, rebalance]
789
+ end
790
+
791
+ class EmptyConcurrentNode < EmptyNode
792
+ # @Overrides
793
+ def insert(key, value)
794
+ ConcurrentNode.new(key, value, self, self)
795
+ end
796
+ end
797
+ EMPTY_CONCURRENT = ConcurrentNode::EmptyConcurrentNode.new.freeze
798
+ end
799
+
800
+ def initialize(default = DEFAULT, &block)
801
+ super
802
+ @root = Atomic.new(ConcurrentNode::EMPTY_CONCURRENT)
803
+ end
804
+
805
+ def root
806
+ @root.get
807
+ end
808
+
809
+ def empty?
810
+ root == ConcurrentNode::EMPTY_CONCURRENT
811
+ end
812
+
813
+ def clear
814
+ @root.set(ConcurrentNode::EMPTY_CONCURRENT)
815
+ end
816
+
817
+ def []=(key, value)
818
+ @root.update { |root|
819
+ root = root.insert(key, value)
820
+ root.set_root
821
+ root.check_height if $DEBUG
822
+ root
823
+ }
824
+ end
825
+ alias insert []=
826
+
827
+ def [](key)
828
+ value = @root.get.retrieve(key)
829
+ if value == Node::UNDEFINED
830
+ default_value
831
+ else
832
+ value
833
+ end
834
+ end
835
+
836
+ def delete(key)
837
+ deleted = nil
838
+ @root.update { |root|
839
+ deleted, root, rebalance = root.delete(key)
840
+ unless root == ConcurrentNode::EMPTY_CONCURRENT
841
+ root.set_root
842
+ root.check_height if $DEBUG
843
+ end
844
+ root
845
+ }
846
+ deleted.value
847
+ end
848
+ end