avl_tree 1.1.3 → 1.2.0

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