avl_tree-1.2.1/0000755000004100000410000000000013416013550013342 5ustar www-datawww-dataavl_tree-1.2.1/test/0000755000004100000410000000000013416013550014321 5ustar www-datawww-dataavl_tree-1.2.1/test/helper.rb0000644000004100000410000000020313416013550016120 0ustar www-datawww-databegin require 'simplecov' SimpleCov.start rescue LoadError end require "test/unit" require "avl_tree" require "red_black_tree" avl_tree-1.2.1/test/test_red_black_tree_thread.rb0000644000004100000410000000134213416013550022161 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('./helper', File.dirname(__FILE__)) class TestRedBlackTree < Test::Unit::TestCase def test_thread h = ConcurrentRedBlackTree.new num = 100000 max = 1000 threads = [] # writers 2.times do threads << Thread.new { num.times do key = rand(max) h[key] = key end } end # deleters 2.times do threads << Thread.new { num.times do key = rand(max) h.delete(key) end } end # readers 2.times do threads << Thread.new { num.times do key = rand(max) h[key] end } end threads.each(&:join) end end avl_tree-1.2.1/test/test_red_black_tree.rb0000644000004100000410000003536013416013550020641 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('./helper', File.dirname(__FILE__)) module RedBlackTreeTest def __test_random h = create_test_target 10000.times do |idx| key = rand(100) h[key] = key key = rand(100) h.delete(key) end end def test_tree_rotate_RR h = create_test_target assert_equal '', h.dump_sexp h['a'] = 1 assert_equal 'a', h.dump_sexp h['b'] = 2 assert_equal '(a - b)', h.dump_sexp h['c'] = 3 assert_equal '(b a c)', h.dump_sexp h['d'] = 4 assert_equal '(b a (c - d))', h.dump_sexp h['e'] = 5 assert_equal '(b a (d c e))', h.dump_sexp end def test_tree_rotate_LL h = create_test_target h['e'] = 1 h['d'] = 2 assert_equal '(e d)', h.dump_sexp h['c'] = 3 assert_equal '(d c e)', h.dump_sexp h['b'] = 4 assert_equal '(d (c b) e)', h.dump_sexp h['a'] = 5 assert_equal '(d (b a c) e)', h.dump_sexp end def test_tree_rotate_RL h = create_test_target h['b'] = 1 h['a'] = 2 h['g'] = 3 h['d'] = 4 h['h'] = 5 assert_equal '(b a (g d h))', h.dump_sexp h['c'] = 6 assert_equal '(b a (g (d c) h))', h.dump_sexp h['e'] = 6 assert_equal '(d (b a c) (g e h))', h.dump_sexp h['f'] = 6 assert_equal '(d (b a c) (g (e - f) h))', h.dump_sexp end def test_tree_rotate_LR h = create_test_target h['g'] = 1 h['b'] = 2 h['h'] = 3 h['i'] = 4 h['a'] = 5 h['d'] = 6 h['0'] = 7 h['c'] = 8 h['e'] = 9 assert_equal '(d (b (a 0) c) (g e (h - i)))', h.dump_sexp h['f'] = 10 assert_equal '(d (b (a 0) c) (g (e - f) (h - i)))', h.dump_sexp end def test_aref_nil h = create_test_target h['abc'] = 1 assert_equal nil, h['def'] end def test_empty h = create_test_target h['abc'] = 0 assert_equal nil, h[''] h[''] = 1 assert_equal 1, h[''] h.delete('') assert_equal nil, h[''] end def test_aref_single h = create_test_target h['abc'] = 1 assert_equal 1, h['abc'] end def test_aref_double h = create_test_target h['abc'] = 1 h['def'] = 2 assert_equal 1, h['abc'] assert_equal 2, h['def'] end def test_aset_override h = create_test_target h['abc'] = 1 h['abc'] = 2 assert_equal 2, h['abc'] end def test_split h = create_test_target h['abcd'] = 1 assert_equal 1, h['abcd'] h['abce'] = 2 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] h['abd'] = 3 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] assert_equal 3, h['abd'] h['ac'] = 4 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] assert_equal 3, h['abd'] assert_equal 4, h['ac'] end def test_split_and_assign h = create_test_target h['ab'] = 1 h['a'] = 2 assert_equal 1, h['ab'] assert_equal 2, h['a'] end def test_push h = create_test_target assert_equal 0, h.size h['a'] = 1 assert_equal 1, h['a'] h['ab'] = 2 assert_equal 1, h['a'] assert_equal 2, h['ab'] h['abc'] = 3 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] h['abd'] = 4 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] h['ac'] = 5 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] assert_equal 5, h['ac'] h['b'] = 6 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] assert_equal ['a', 'ab', 'abc', 'abd', 'ac', 'b'].sort, h.keys.sort assert_equal 6, h.size end def test_different_type h = create_test_target h['a'] = 1 assert_raise(TypeError) do h[3.3] = 2 end assert_nil h[3.3] end def test_delete_leaf h = create_test_target h['b'] = 1 h['a'] = 2 h['c'] = 3 assert_equal 2, h['a'] h.delete('a') assert_equal nil, h['a'] end def test_delete_leaf_single_rotation h = create_test_target h['b'] = 1 h['a'] = 2 h['d'] = 3 h['c'] = 4 h['e'] = 5 assert_equal '(b a (d c e))', h.dump_sexp h.delete('a') assert_equal '(d (b - c) e)', h.dump_sexp end def test_delete_leaf_single_rotation_right h = create_test_target h['d'] = 1 h['e'] = 2 h['b'] = 3 h['c'] = 4 h['a'] = 5 assert_equal '(d (b a c) e)', h.dump_sexp h.delete('e') assert_equal '(b a (d c))', h.dump_sexp end def test_delete_leaf_double_rotation h = create_test_target h['b'] = 1 h['a'] = 2 h['e'] = 3 h['0'] = 4 h['c'] = 5 h['f'] = 6 h['d'] = 7 assert_equal '(b (a 0) (e (c - d) f))', h.dump_sexp h.delete('0') assert_equal '(b a (e (c - d) f))', h.dump_sexp h.delete('a') assert_equal '(e (c b d) f)', h.dump_sexp end def test_delete_leaf_double_rotation_right h = create_test_target h['d'] = 1 h['e'] = 2 h['a'] = 3 h['f'] = 4 h['c'] = 5 h['0'] = 6 h['b'] = 7 assert_equal '(d (a 0 (c b)) (e - f))', h.dump_sexp h.delete('f') assert_equal '(d (a 0 (c b)) e)', h.dump_sexp h.delete('e') assert_equal '(a 0 (c b d))', h.dump_sexp end def test_delete_node_right h = create_test_target h['c'] = 1 h['b'] = 2 h['g'] = 3 h['a'] = 4 h['e'] = 5 h['i'] = 6 h['d'] = 7 h['f'] = 8 h['h'] = 9 h['j'] = 10 assert_equal '(e (c (b a) d) (g f (i h j)))', h.dump_sexp h.delete('g') assert_equal '(e (c (b a) d) (h f (i - j)))', h.dump_sexp end def test_delete_node_left h = create_test_target h['h'] = 1 h['i'] = 2 h['d'] = 3 h['j'] = 4 h['f'] = 5 h['b'] = 6 h['g'] = 7 h['e'] = 8 h['c'] = 9 h['a'] = 10 assert_equal '(f (d (b a c) e) (h g (i - j)))', h.dump_sexp h.delete('d') assert_equal '(f (b a (e c)) (h g (i - j)))', h.dump_sexp end def test_delete_root h = create_test_target h['b'] = 1 h['a'] = 2 h['c'] = 3 assert_equal 1, h['b'] assert_equal '(b a c)', h.dump_sexp h.delete('b') assert_equal '(c a)', h.dump_sexp assert_equal nil, h['b'] end def test_delete h = create_test_target h['a'] = 1 h['ab'] = 2 h['abc'] = 3 h['abd'] = 4 h['ac'] = 5 h['b'] = 6 assert_equal 6, h.size assert_equal nil, h.delete('XXX') # delete leaf assert_equal 4, h.delete('abd') assert_equal 5, h.size assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] # delete single leaf node assert_equal 2, h.delete('ab') assert_equal 4, h.size assert_equal 1, h['a'] assert_equal nil, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] # delete multiple leaf node assert_equal 1, h.delete('a') assert_equal 3, h.size assert_equal nil, h['a'] assert_equal nil, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] assert_equal ['abc', 'ac', 'b'].sort, h.keys.sort # delete rest assert_equal 3, h.delete('abc') assert_equal 5, h.delete('ac') assert_equal 6, h.delete('b') assert_equal 0, h.size assert h.empty? end def test_delete_right h = create_test_target h['f'] = 1 h['e'] = 2 h['d'] = 3 h['c'] = 4 h['b'] = 5 h['a'] = 6 assert_equal 6, h.size assert_equal nil, h.delete('XXX') # delete leaf assert_equal 4, h.delete('c') assert_equal 5, h.size assert_equal 1, h['f'] assert_equal 2, h['e'] assert_equal 3, h['d'] assert_equal nil, h['c'] assert_equal 5, h['b'] assert_equal 6, h['a'] # delete single leaf node assert_equal 2, h.delete('e') assert_equal 4, h.size assert_equal 1, h['f'] assert_equal nil, h['e'] assert_equal 3, h['d'] assert_equal nil, h['c'] assert_equal 5, h['b'] assert_equal 6, h['a'] # delete multiple leaf node assert_equal 1, h.delete('f') assert_equal 3, h.size assert_equal nil, h['f'] assert_equal nil, h['e'] assert_equal 3, h['d'] assert_equal nil, h['c'] assert_equal 5, h['b'] assert_equal 6, h['a'] assert_equal ['a', 'b', 'd'].sort, h.keys.sort # delete rest assert_equal 3, h.delete('d') assert_equal 5, h.delete('b') assert_equal 6, h.delete('a') assert_equal 0, h.size assert h.empty? end def test_delete_compaction_middle h = create_test_target h['a'] = 1 h['abc'] = 2 h['bb'] = 3 h['abcdefghi'] = 4 h['abcdefghijzz'] = 5 h['abcdefghikzz'] = 6 assert_equal 6, h.dump_tree.split($/).size h.delete('a') assert_equal 5, h.dump_tree.split($/).size h['a'] = 1 assert_equal 6, h.dump_tree.split($/).size end def test_delete_compaction_leaf h = create_test_target h['a'] = 1 h['abc'] = 2 h['bb'] = 3 h['abcdefghijzz'] = 4 assert_equal 4, h.dump_tree.split($/).size h['abcdefghikzz'] = 5 assert_equal 5, h.dump_tree.split($/).size h.delete('abcdefghijzz') assert_equal 4, h.dump_tree.split($/).size h['abcdefghijzz'] = 4 assert_equal 5, h.dump_tree.split($/).size end def test_delete_balanced_rotate_left h = create_test_target h['f'] = 1 h['c'] = 100 h['l'] = 1 h['b'] = 100 h['e'] = 1 h['i'] = 1 h['m'] = 1 h['a'] = 100 h['d'] = 1 h['h'] = 1 h['k'] = 1 h['n'] = 1 h['j'] = 1 h['g'] = 1 assert_equal '(f (c (b a) (e d)) (l (i (h g) (k j)) (m - n)))', h.dump_sexp assert_equal 14, h.size # reduce black from the left node assert_equal 100, h.delete('b') assert_equal 100, h.delete('a') # double rotation at 'l' and 'f' node assert_equal 100, h.delete('c') assert_equal 11, h.size assert_equal '(i (f (d - e) (h g)) (l (k j) (m - n)))', h.dump_sexp end def test_delete_balanced_rotate_right h = create_test_target h['i'] = 1 h['l'] = 100 h['c'] = 1 h['m'] = 100 h['j'] = 1 h['f'] = 1 h['b'] = 1 h['n'] = 100 h['k'] = 1 h['g'] = 1 h['d'] = 1 h['a'] = 1 h['e'] = 1 h['h'] = 1 assert_equal '(i (c (b a) (f (d - e) (g - h))) (l (j - k) (m - n)))', h.dump_sexp assert_equal 14, h.size # reduce black from the left node assert_equal 100, h.delete('m') assert_equal 100, h.delete('n') # double rotation at 'c' and 'i' node assert_equal 100, h.delete('l') assert_equal 11, h.size assert_equal '(f (c (b a) (d - e)) (i (g - h) (k j)))', h.dump_sexp end def test_delete_different_type h = create_test_target h['a'] = 1 h['abc'] = 2 h['bb'] = 3 assert_raise(TypeError) do h.delete(3.3) end end def test_each h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.to_a.sort_by { |k, v| k }, h.each.sort_by { |k, v| k } # values = [] h.each do |k, v| values << [k, v] end assert_equal s.to_a.sort_by { |k, v| k }, values.sort_by { |k, v| k } end def test_each_key h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.keys.sort, h.each_key.sort # values = [] h.each_key do |k| values << k end assert_equal s.keys.sort, values.sort end def test_each_value h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6, 'azzzzz' => 6 } s.each do |k, v| h[k] = v end assert_equal s.values.sort, h.each_value.sort # values = [] h.each_value do |v| values << v end assert_equal s.values.sort, values.sort end def test_keys h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.keys.sort, h.keys.sort end def test_values h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.values.sort, h.values.sort end def test_to_s h = create_test_target h[5] = 1 assert_equal 1, h[5] assert_nil h["5"] end def test_key? h = create_test_target assert !h.key?('a') s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert h.key?('a') end def test_default assert_raise(ArgumentError) do create_test_target('both') { :not_allowed } end h = create_test_target('abc') assert_equal 'abc', h['foo'] assert_equal 'abc', h['bar'] assert h['baz'].object_id == h['qux'].object_id h = create_test_target { [1, 2] } assert_equal [1, 2], h['foo'] assert_equal [1, 2], h['bar'] assert h['baz'].object_id != h['qux'].object_id end def test_to_hash h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s, h.to_hash end def test_clear h = create_test_target s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s, h.to_hash h.clear assert_equal 0, h.size assert h.to_hash.empty? end def test_non_string_keys h = create_test_target h[1.3] = 'a' h[4.3] = 'b' assert_equal [1.3, 'a' ], h.first end def test_values_for_empty_tree h = create_test_target assert_equal [], h.values end def test_check_height_on_empty_tree h = create_test_target assert_nothing_raised { h.root.check_height } end if RUBY_VERSION >= '1.9.0' # In contrast to RadixTree, RedBlackTree just uses String#<=> as-is def test_encoding h = create_test_target s = { '$B$"$"(B' => 1, '$B$"$$(B' => 2, '$B$$$$(B' => 3, '$B$$$&(B' => 4, '$B$"(B' => 5, '$B$"$$$&(B' => 6 } s.each do |k, v| h[k] = v end assert_equal 6, h.size s.each do |k, v| assert_equal v, h[k] end str = '$B$"$"(B' str.force_encoding('US-ASCII') # it's nil for RadixTree because RadixTree uses char-to-char comparison assert_equal 1, h[str] end end end class TestRedBlackTree < Test::Unit::TestCase include RedBlackTreeTest def create_test_target(*a, &b) RedBlackTree.new(*a, &b) end end class TestConcurrentRedBlackTree < Test::Unit::TestCase include RedBlackTreeTest def create_test_target(*a, &b) ConcurrentRedBlackTree.new(*a, &b) end end avl_tree-1.2.1/test/test_avl_tree.rb0000644000004100000410000002633113416013550017513 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('./helper', File.dirname(__FILE__)) class TestAVLTree < Test::Unit::TestCase def test_tree_rotate_RR h = AVLTree.new assert_equal '', h.dump_sexp h['a'] = 1 assert_equal 'a', h.dump_sexp h['b'] = 2 assert_equal '(a - b)', h.dump_sexp h['c'] = 3 assert_equal '(b a c)', h.dump_sexp h['d'] = 4 assert_equal '(b a (c - d))', h.dump_sexp h['e'] = 5 assert_equal '(b a (d c e))', h.dump_sexp end def test_tree_rotate_LL h = AVLTree.new h['e'] = 1 h['d'] = 2 assert_equal '(e d)', h.dump_sexp h['c'] = 3 assert_equal '(d c e)', h.dump_sexp h['b'] = 4 assert_equal '(d (c b) e)', h.dump_sexp h['a'] = 5 assert_equal '(d (b a c) e)', h.dump_sexp end def test_tree_rotate_RL h = AVLTree.new h['b'] = 1 h['a'] = 2 h['e'] = 3 h['d'] = 4 h['f'] = 5 assert_equal '(b a (e d f))', h.dump_sexp h['c'] = 6 assert_equal '(d (b a c) (e - f))', h.dump_sexp end def test_tree_rotate_LR h = AVLTree.new h['g'] = 1 h['b'] = 2 h['h'] = 3 h['i'] = 4 h['a'] = 5 h['d'] = 6 h['0'] = 7 h['c'] = 8 h['e'] = 9 assert_equal '(g (b (a 0) (d c e)) (h - i))', h.dump_sexp h['f'] = 10 assert_equal '(d (b (a 0) c) (g (e - f) (h - i)))', h.dump_sexp end def test_aref_nil h = AVLTree.new h['abc'] = 1 assert_equal nil, h['def'] end def test_empty h = AVLTree.new h['abc'] = 0 assert_equal nil, h[''] h[''] = 1 assert_equal 1, h[''] h.delete('') assert_equal nil, h[''] end def test_aref_single h = AVLTree.new h['abc'] = 1 assert_equal 1, h['abc'] end def test_aref_double h = AVLTree.new h['abc'] = 1 h['def'] = 2 assert_equal 1, h['abc'] assert_equal 2, h['def'] end def test_aset_override h = AVLTree.new h['abc'] = 1 h['abc'] = 2 assert_equal 2, h['abc'] end def test_split h = AVLTree.new h['abcd'] = 1 assert_equal 1, h['abcd'] h['abce'] = 2 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] h['abd'] = 3 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] assert_equal 3, h['abd'] h['ac'] = 4 assert_equal 1, h['abcd'] assert_equal 2, h['abce'] assert_equal 3, h['abd'] assert_equal 4, h['ac'] end def test_split_and_assign h = AVLTree.new h['ab'] = 1 h['a'] = 2 assert_equal 1, h['ab'] assert_equal 2, h['a'] end def test_push h = AVLTree.new assert_equal 0, h.size h['a'] = 1 assert_equal 1, h['a'] h['ab'] = 2 assert_equal 1, h['a'] assert_equal 2, h['ab'] h['abc'] = 3 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] h['abd'] = 4 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] h['ac'] = 5 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] assert_equal 5, h['ac'] h['b'] = 6 assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal 4, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] assert_equal ['a', 'ab', 'abc', 'abd', 'ac', 'b'].sort, h.keys.sort assert_equal 6, h.size end def test_different_type h = AVLTree.new h['a'] = 1 assert_raise(TypeError) do h[3.3] = 2 end assert_nil h[3.3] end def test_delete_leaf h = AVLTree.new h['b'] = 1 h['a'] = 2 h['c'] = 3 assert_equal 2, h['a'] h.delete('a') assert_equal nil, h['a'] end def test_delete_leaf_single_rotation h = AVLTree.new h['b'] = 1 h['a'] = 2 h['d'] = 3 h['c'] = 4 h['e'] = 5 assert_equal '(b a (d c e))', h.dump_sexp h.delete('a') assert_equal '(d (b - c) e)', h.dump_sexp end def test_delete_leaf_double_rotation h = AVLTree.new h['b'] = 1 h['a'] = 2 h['e'] = 3 h['0'] = 4 h['c'] = 5 h['f'] = 6 h['d'] = 7 assert_equal '(b (a 0) (e (c - d) f))', h.dump_sexp h.delete('0') assert_equal '(c (b a) (e d f))', h.dump_sexp end def test_delete_node_right h = AVLTree.new h['c'] = 1 h['b'] = 2 h['g'] = 3 h['a'] = 4 h['e'] = 5 h['i'] = 6 h['d'] = 7 h['f'] = 8 h['h'] = 9 h['j'] = 10 assert_equal '(c (b a) (g (e d f) (i h j)))', h.dump_sexp h.delete('g') assert_equal '(c (b a) (h (e d f) (i - j)))', h.dump_sexp end def test_delete_node_left h = AVLTree.new h['c'] = 1 h['b'] = 2 h['d'] = 3 h['a'] = 4 assert_equal '(c (b a) d)', h.dump_sexp h.delete('b') assert_equal '(c a d)', h.dump_sexp end def test_delete_root h = AVLTree.new h['b'] = 1 h['a'] = 2 h['c'] = 3 assert_equal 1, h['b'] assert_equal '(b a c)', h.dump_sexp h.delete('b') assert_equal '(c a)', h.dump_sexp assert_equal nil, h['b'] end def test_delete h = AVLTree.new h['a'] = 1 h['ab'] = 2 h['abc'] = 3 h['abd'] = 4 h['ac'] = 5 h['b'] = 6 assert_equal 6, h.size assert_equal nil, h.delete('XXX') # delete leaf assert_equal 4, h.delete('abd') assert_equal 5, h.size assert_equal 1, h['a'] assert_equal 2, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] # delete single leaf node assert_equal 2, h.delete('ab') assert_equal 4, h.size assert_equal 1, h['a'] assert_equal nil, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] # delete multiple leaf node assert_equal 1, h.delete('a') assert_equal 3, h.size assert_equal nil, h['a'] assert_equal nil, h['ab'] assert_equal 3, h['abc'] assert_equal nil, h['abd'] assert_equal 5, h['ac'] assert_equal 6, h['b'] assert_equal ['abc', 'ac', 'b'].sort, h.keys.sort # delete rest assert_equal 3, h.delete('abc') assert_equal 5, h.delete('ac') assert_equal 6, h.delete('b') assert_equal 0, h.size assert h.empty? end def test_delete_compaction_middle h = AVLTree.new h['a'] = 1 h['abc'] = 2 h['bb'] = 3 h['abcdefghi'] = 4 h['abcdefghijzz'] = 5 h['abcdefghikzz'] = 6 assert_equal 6, h.dump_tree.split($/).size h.delete('a') assert_equal 5, h.dump_tree.split($/).size h['a'] = 1 assert_equal 6, h.dump_tree.split($/).size end def test_delete_compaction_leaf h = AVLTree.new h['a'] = 1 h['abc'] = 2 h['bb'] = 3 h['abcdefghijzz'] = 4 assert_equal 4, h.dump_tree.split($/).size h['abcdefghikzz'] = 5 assert_equal 5, h.dump_tree.split($/).size h.delete('abcdefghijzz') assert_equal 4, h.dump_tree.split($/).size h['abcdefghijzz'] = 4 assert_equal 5, h.dump_tree.split($/).size end def test_delete_different_type h = AVLTree.new h['a'] = 1 h['abc'] = 2 h['bb'] = 3 assert_raise(TypeError) do h.delete(3.3) end end def test_each h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.to_a.sort_by { |k, v| k }, h.each.sort_by { |k, v| k } # values = [] h.each do |k, v| values << [k, v] end assert_equal s.to_a.sort_by { |k, v| k }, values.sort_by { |k, v| k } end def test_each_key h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.keys.sort, h.each_key.sort # values = [] h.each_key do |k| values << k end assert_equal s.keys.sort, values.sort end def test_each_value h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6, 'azzzzz' => 6 } s.each do |k, v| h[k] = v end assert_equal s.values.sort, h.each_value.sort # values = [] h.each_value do |v| values << v end assert_equal s.values.sort, values.sort end def test_keys h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.keys.sort, h.keys.sort end def test_values h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s.values.sort, h.values.sort end def test_to_s h = AVLTree.new h[5] = 1 assert_equal 1, h[5] assert_nil h["5"] end def test_key? h = AVLTree.new assert !h.key?('a') s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert h.key?('a') end def test_default assert_raise(ArgumentError) do AVLTree.new('both') { :not_allowed } end h = AVLTree.new('abc') assert_equal 'abc', h['foo'] assert_equal 'abc', h['bar'] assert h['baz'].object_id == h['qux'].object_id h = AVLTree.new { [1, 2] } assert_equal [1, 2], h['foo'] assert_equal [1, 2], h['bar'] assert h['baz'].object_id != h['qux'].object_id end def test_to_hash h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s, h.to_hash end def test_clear h = AVLTree.new s = { 'aa' => 1, 'ab' => 2, 'bb' => 3, 'bc' => 4, 'a' => 5, 'abc' => 6 } s.each do |k, v| h[k] = v end assert_equal s, h.to_hash h.clear assert_equal 0, h.size assert h.to_hash.empty? end def test_non_string_keys h = AVLTree.new h[1.3] = 'a' h[4.3] = 'b' assert_equal [1.3, 'a' ], h.first end def test_values_for_empty_tree h = AVLTree.new assert_equal [], h.values end def test_height h = AVLTree.new assert_equal 0, h.height h[1] = true assert_equal 1, h.height h[2] = true assert_equal 2, h.height h[3] = true assert_equal 2, h.height h[4] = true assert_equal 3, h.height h[5] = true assert_equal 3, h.height h[6] = true assert_equal 3, h.height h[7] = true assert_equal 3, h.height h[8] = true assert_equal 4, h.height h.delete(8) assert_equal 3, h.height h.delete(7) assert_equal 3, h.height h.delete(6) assert_equal 3, h.height h.delete(5) assert_equal 3, h.height h.delete(4) assert_equal 2, h.height h.delete(3) assert_equal 2, h.height h.delete(2) assert_equal 1, h.height h.delete(1) assert_equal 0, h.height end if RUBY_VERSION >= '1.9.0' # In contrast to RadixTree, AVLTree just uses String#<=> as-is def test_encoding h = AVLTree.new s = { '$B$"$"(B' => 1, '$B$"$$(B' => 2, '$B$$$$(B' => 3, '$B$$$&(B' => 4, '$B$"(B' => 5, '$B$"$$$&(B' => 6 } s.each do |k, v| h[k] = v end assert_equal 6, h.size s.each do |k, v| assert_equal v, h[k] end str = '$B$"$"(B' str.force_encoding('US-ASCII') # it's nil for RadixTree because RadixTree uses char-to-char comparison assert_equal 1, h[str] end end end avl_tree-1.2.1/README.md0000644000004100000410000000175313416013550014627 0ustar www-datawww-data# AVL tree, Red-black tree in Ruby avl_tree - AVL tree, Red-black tree and Lock-free Red black tree in Ruby Copyright (C) 2014 Hiroshi Nakamura ## Usage You can use AVLTree, RedBlackTree or ConcurrentRedBlackTree just as a replacement of Hash. @points = Hash.new ... @points[score] = person ... @points.each do |score, person| ... end -> require 'avl_tree' @points = AVLTree.new require 'red_black_tree' @points = RedBlackTree.new @points = ConcurrentRedBlackTree.new AVLTree and RedBlackTree are faster but not thread-safe. Use ConcurrentRedBlackTree in multi-thread environment. ## Author Name:: Hiroshi Nakamura E-mail:: nahi@ruby-lang.org Project web site:: http://github.com/nahi/avl_tree ## License This program is copyrighted free software by Hiroshi Nakamura. You can redistribute it and/or modify it under the same terms of Ruby's license; either the dual license version in 2003, or any later version. avl_tree-1.2.1/avl_tree.gemspec0000644000004100000410000000266213416013550016516 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: avl_tree 1.2.1 ruby lib Gem::Specification.new do |s| s.name = "avl_tree".freeze s.version = "1.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Hiroshi Nakamura".freeze] s.date = "2014-09-28" s.email = "nahi@ruby-lang.org".freeze s.files = ["README.md".freeze, "bench/bench.rb".freeze, "bench/bench_element_size.rb".freeze, "bench/bench_thread.rb".freeze, "bench/profile.rb".freeze, "lib/avl_tree.rb".freeze, "lib/red_black_tree.rb".freeze, "test/helper.rb".freeze, "test/test_avl_tree.rb".freeze, "test/test_red_black_tree.rb".freeze, "test/test_red_black_tree_thread.rb".freeze] s.homepage = "http://github.com/nahi/avl_tree".freeze s.rubygems_version = "2.5.2.1".freeze s.summary = "AVL tree, Red black tree and Lock-free Red black tree in Ruby".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 1.1"]) else s.add_dependency(%q.freeze, ["~> 1.1"]) end else s.add_dependency(%q.freeze, ["~> 1.1"]) end end avl_tree-1.2.1/bench/0000755000004100000410000000000013416013550014421 5ustar www-datawww-dataavl_tree-1.2.1/bench/bench_element_size.rb0000644000004100000410000000154113416013550020571 0ustar www-datawww-datarequire 'benchmark' require 'radix_tree' # gem install radix_tree require 'avl_tree' require 'openssl' times = 100000 key_size = 10 def aset(h, keys) keys.each do |k| h[k] = 1 end end def aref(h, keys) keys.each do |k| h[k] end end def delete(h, keys) keys.each do |k| h.delete(k) end end def run(bm, h, keys) name = h.class.name bm.report("#{name} aset (#{keys.size})") do aset(h, keys) end bm.report("#{name} aref (#{keys.size})") do aref(h, keys) end bm.report("#{name} delete (#{keys.size})") do delete(h, keys) end end keys = [] 1000000.times do keys << OpenSSL::Random.random_bytes(key_size) end 1.upto(100) do |idx| elements = idx * 10000 Benchmark.bm(30) do |bm| #run(bm, Hash.new, keys[0, elements]) #run(bm, RadixTree.new, keys) run(bm, AVLTree.new, keys[0, elements]) end end avl_tree-1.2.1/bench/bench.rb0000644000004100000410000000133613416013550016030 0ustar www-datawww-datarequire 'benchmark' require 'avl_tree' require 'red_black_tree' require 'openssl' #random = Random.new(0) TIMES = 100000 key_size = 10 def aset(h, keys) keys.each do |k| h[k] = 1 end end def aref(h, keys) keys.each do |k| h[k] end end def delete(h, keys) keys.each do |k| h.delete(k) end end def run(bm, h, keys) name = h.class.name bm.report("#{name} aset") do aset(h, keys) end bm.report("#{name} aref") do aref(h, keys) end bm.report("#{name} delete") do delete(h, keys) end end keys = [] TIMES.times do keys << OpenSSL::Random.random_bytes(key_size) end Benchmark.bmbm do |bm| run(bm, Hash.new, keys) run(bm, AVLTree.new, keys) run(bm, RedBlackTree.new, keys) end avl_tree-1.2.1/bench/bench_thread.rb0000644000004100000410000000122213416013550017351 0ustar www-datawww-datarequire 'benchmark' require 'red_black_tree' Benchmark.bmbm do |bm| bm.report do h = ConcurrentRedBlackTree.new num = 100000 max = 1000 threads = [] # writers 2.times do threads << Thread.new { num.times do key = rand(max) h[key] = key end } end # deleters 2.times do threads << Thread.new { num.times do key = rand(max) h.delete(key) end } end # readers 2.times do threads << Thread.new { num.times do key = rand(max) h[key] end } end threads.each(&:join) end end avl_tree-1.2.1/bench/profile.rb0000644000004100000410000000041113416013550016402 0ustar www-datawww-datarequire File.expand_path('../lib/red_black_tree', File.dirname(__FILE__)) random = Random.new(0) TIMES = 50000 key_size = 10 h = RedBlackTree.new TIMES.times do h[random.bytes(key_size)] = 1 #h[random.bytes(key_size)] #h.delete(random.bytes(key_size)) end avl_tree-1.2.1/lib/0000755000004100000410000000000013416013550014110 5ustar www-datawww-dataavl_tree-1.2.1/lib/avl_tree.rb0000644000004100000410000001642713416013550016250 0ustar www-datawww-dataclass AVLTree include Enumerable class Node UNDEFINED = Object.new attr_reader :key, :value, :height attr_reader :left, :right def initialize(key, value) @key, @value = key, value @left = @right = EMPTY @height = 1 end def empty? false end def size @left.size + 1 + @right.size end # inorder def each(&block) @left.each(&block) yield [@key, @value] @right.each(&block) end def each_key each do |k, v| yield k end end def each_value each do |k, v| yield v end end def keys collect { |k, v| k } end def values collect { |k, v| v } end # returns new_root def insert(key, value) case key <=> @key when -1 @left = @left.insert(key, value) when 0 @value = value when 1 @right = @right.insert(key, value) else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end rotate end # returns value def retrieve(key) case key <=> @key when -1 @left.retrieve(key) when 0 @value when 1 @right.retrieve(key) else nil end end # returns [deleted_node, new_root] def delete(key) case key <=> @key when -1 deleted, @left = @left.delete(key) [deleted, self.rotate] when 0 [self, delete_self.rotate] when 1 deleted, @right = @right.delete(key) [deleted, self.rotate] else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end end def delete_min if @left.empty? [self, delete_self] else deleted, @left = @left.delete_min [deleted, rotate] end end def delete_max if @right.empty? [self, delete_self] else deleted, @right = @right.delete_max [deleted, rotate] end end def dump_tree(io, indent = '') @right.dump_tree(io, indent + ' ') io << indent << sprintf("#<%s:0x%010x %d %s> => %s", self.class.name, __id__, height, @key.inspect, @value.inspect) << $/ @left.dump_tree(io, indent + ' ') end def dump_sexp left = @left.dump_sexp right = @right.dump_sexp if left or right '(' + [@key, left || '-', right].compact.join(' ') + ')' else @key end end # for debugging def check_height @left.check_height @right.check_height lh = @left.height rh = @right.height if (lh - rh).abs > 1 puts dump_tree(STDERR) raise "height unbalanced: #{lh} #{height} #{rh}" end if (lh > rh ? lh : rh) + 1 != height puts dump_tree(STDERR) raise "height calc failure: #{lh} #{height} #{rh}" end end protected def left=(left) @left = left end def right=(right) @right = right end def update_height @height = (@left.height > @right.height ? @left.height : @right.height) + 1 end def rotate case @left.height - @right.height when +2 if @left.left.height < @left.right.height @left = @left.rotate_left end root = rotate_right when -2 if @right.left.height > @right.right.height @right = @right.rotate_right end root = rotate_left else root = self end root.update_height root end # Right single rotation # (B a (D c E)) where D-a > 1 && E > c --> (D (B a c) E) # # B D # / \ / \ # a D -> B E # / \ / \ # c E a c # def rotate_left root = @right @right = root.left root.left = self root.left.update_height root end # Left single rotation # (D (B A c) e) where B-e > 1 && A > c --> (B A (D c e)) # # D B # / \ / \ # B e -> A D # / \ / \ # A c c e # def rotate_right root = @left @left = root.right root.right = self root.right.update_height root end private def delete_self if @left.empty? and @right.empty? deleted = EMPTY elsif @right.height < @left.height deleted, new_left = @left.delete_max deleted.left, deleted.right = new_left, @right else deleted, new_right = @right.delete_min deleted.left, deleted.right = @left, new_right end deleted end def collect pool = [] each do |key, value| pool << yield(key, value) end pool end class EmptyNode < Node def initialize @value = nil @height = 0 end def empty? true end def size 0 end def each(&block) # intentionally blank end # returns new_root def insert(key, value) Node.new(key, value) end # returns value def retrieve(key) UNDEFINED end # returns [deleted_node, new_root] def delete(key) [self, self] end def dump_tree(io, indent = '') # intentionally blank end def dump_sexp # intentionally blank end def rotate self end def update_height # intentionally blank end # for debugging def check_height # intentionally blank end end EMPTY = Node::EmptyNode.new.freeze end DEFAULT = Object.new attr_accessor :default attr_reader :default_proc def initialize(default = DEFAULT, &block) if block && default != DEFAULT raise ArgumentError, 'wrong number of arguments' end @root = Node::EMPTY @default = default @default_proc = block end def empty? @root == Node::EMPTY end def size @root.size end alias length size def height @root.height end def each(&block) if block_given? @root.each(&block) self else Enumerator.new(@root) end end alias each_pair each def each_key if block_given? @root.each do |k, v| yield k end self else Enumerator.new(@root, :each_key) end end def each_value if block_given? @root.each do |k, v| yield v end self else Enumerator.new(@root, :each_value) end end def keys @root.keys end def values @root.values end def clear @root = Node::EMPTY end def []=(key, value) @root = @root.insert(key, value) end alias insert []= def key?(key) @root.retrieve(key) != Node::UNDEFINED end alias has_key? key? def [](key) value = @root.retrieve(key) if value == Node::UNDEFINED default_value else value end end def delete(key) deleted, @root = @root.delete(key) deleted.value end def dump_tree(io = '') @root.dump_tree(io) io << $/ io end def dump_sexp @root.dump_sexp || '' end def to_hash inject({}) { |r, (k, v)| r[k] = v; r } end private def default_value if @default != DEFAULT @default elsif @default_proc @default_proc.call else nil end end end avl_tree-1.2.1/lib/red_black_tree.rb0000644000004100000410000004614213416013550017371 0ustar www-datawww-datarequire 'atomic' class RedBlackTree include Enumerable class Node UNDEFINED = Object.new attr_reader :key, :value, :color attr_reader :left, :right def initialize(key, value, left, right, color = :RED) @key = key @value = value @left = left @right = right # new node is added as RED @color = color end def set_root @color = :BLACK end def red? @color == :RED end def black? @color == :BLACK end def empty? false end def size @left.size + 1 + @right.size end # inorder def each(&block) @left.each(&block) yield [@key, @value] @right.each(&block) end def each_key each do |k, v| yield k end end def each_value each do |k, v| yield v end end def keys collect { |k, v| k } end def values collect { |k, v| v } end # returns new_root def insert(key, value) ret = self case key <=> @key when -1 @left = @left.insert(key, value) if black? and @right.black? and @left.red? and !@left.children_color?(:BLACK) ret = rebalance_for_left_insert end when 0 @value = value when 1 @right = @right.insert(key, value) if black? and @left.black? and @right.red? and !@right.children_color?(:BLACK) ret = rebalance_for_right_insert end else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end ret.pullup_red end # returns value def retrieve(key) case key <=> @key when -1 @left.retrieve(key) when 0 @value when 1 @right.retrieve(key) else nil end end # returns [deleted_node, new_root, is_rebalance_needed] def delete(key) ret = self case key <=> @key when -1 deleted, @left, rebalance = @left.delete(key) if rebalance ret, rebalance = rebalance_for_left_delete end when 0 deleted = self ret, rebalance = delete_node when 1 deleted, @right, rebalance = @right.delete(key) if rebalance ret, rebalance = rebalance_for_right_delete end else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end [deleted, ret, rebalance] end def dump_tree(io, indent = '') @right.dump_tree(io, indent + ' ') io << indent << sprintf("#<%s:0x%010x %s %s> => %s", self.class.name, __id__, @color, @key.inspect, @value.inspect) << $/ @left.dump_tree(io, indent + ' ') end def dump_sexp left = @left.dump_sexp right = @right.dump_sexp if left or right '(' + [@key, left || '-', right].compact.join(' ') + ')' else @key end end # for debugging def check_height lh = @left.nil? || @left.empty? ? 0 : @left.check_height rh = @right.nil? || @right.empty? ? 0 : @right.check_height if red? if @left.red? or @right.red? puts dump_tree(STDERR) raise 'red/red assertion failed' end else if lh != rh puts dump_tree(STDERR) raise "black height unbalanced: #{lh} #{rh}" end end (lh > rh ? lh : rh) + (black? ? 1 : 0) end protected def children_color?(color) @right.color == @left.color && @right.color == color end def color=(color) @color = color end def left=(left) @left = left end def right=(right) @right = right end def color_flip(other) @color, other.color = other.color, @color end def delete_min if @left.empty? [self, *delete_node] else ret = self deleted, @left, rebalance = @left.delete_min if rebalance ret, rebalance = rebalance_for_left_delete end [deleted, ret, rebalance] end end # trying to rebalance when the left sub-tree is 1 level lower than the right def rebalance_for_left_delete ret = self rebalance = false if black? if @right.black? if @right.children_color?(:BLACK) # make whole sub-tree 1 level lower and ask rebalance @right.color = :RED rebalance = true else # move 1 black from the right to the left by single/double rotation ret = balanced_rotate_left end else # flip this sub-tree into another type of 3-children node ret = rotate_left # try to rebalance in sub-tree ret.left, rebalance = ret.left.rebalance_for_left_delete raise 'should not happen' if rebalance end else # red if @right.children_color?(:BLACK) # make right sub-tree 1 level lower color_flip(@right) else # move 1 black from the right to the left by single/double rotation ret = balanced_rotate_left end end [ret, rebalance] end # trying to rebalance when the right sub-tree is 1 level lower than the left # See rebalance_for_left_delete. def rebalance_for_right_delete ret = self rebalance = false if black? if @left.black? if @left.children_color?(:BLACK) @left.color = :RED rebalance = true else ret = balanced_rotate_right end else ret = rotate_right ret.right, rebalance = ret.right.rebalance_for_right_delete raise 'should not happen' if rebalance end else # red if @left.children_color?(:BLACK) color_flip(@left) else ret = balanced_rotate_right end end [ret, rebalance] end # move 1 black from the right to the left by single/double rotation def balanced_rotate_left if @right.left.red? and @right.right.black? @right = @right.rotate_right end ret = rotate_left ret.right.color = ret.left.color = :BLACK ret end # move 1 black from the left to the right by single/double rotation def balanced_rotate_right if @left.right.red? and @left.left.black? @left = @left.rotate_left end ret = rotate_right ret.right.color = ret.left.color = :BLACK ret end # Right single rotation # (b a (D c E)) where D and E are RED --> (d (B a c) E) # # b d # / \ / \ # a D -> B E # / \ / \ # c E a c # def rotate_left root = @right @right = root.left root.left = self root.color_flip(root.left) root end # Left single rotation # (d (B A c) e) where A and B are RED --> (b A (D c e)) # # d b # / \ / \ # B e -> A D # / \ / \ # A c c e # def rotate_right root = @left @left = root.right root.right = self root.color_flip(root.right) root end # Pull up red nodes # (b (A C)) where A and C are RED --> (B (a c)) # # b B # / \ -> / \ # A C a c # def pullup_red if black? and children_color?(:RED) @left.color = @right.color = :BLACK self.color = :RED end self end private # trying to rebalance when the left sub-tree is 1 level higher than the right # precondition: self is black and @left is red def rebalance_for_left_insert # move 1 black from the left to the right by single/double rotation if @left.right.red? @left = @left.rotate_left end rotate_right end # trying to rebalance when the right sub-tree is 1 level higher than the left # See rebalance_for_left_insert. def rebalance_for_right_insert if @right.left.red? @right = @right.rotate_right end rotate_left end def delete_node rebalance = false if @left.empty? and @right.empty? # just remove this node and ask rebalance to the parent new_root = EMPTY if black? rebalance = true end elsif @left.empty? or @right.empty? # pick the single children new_root = @left.empty? ? @right : @left if black? # keep the color black raise 'should not happen' unless new_root.red? color_flip(new_root) else # just remove the red node end else # pick the minimum node from the right sub-tree and replace self with it deleted, @right, rebalance = @right.delete_min new_root = Node.new(deleted.key, deleted.value, @left, @right, @color) if rebalance new_root, rebalance = new_root.rebalance_for_right_delete end end [new_root, rebalance] end def collect pool = [] each do |key, value| pool << yield(key, value) end pool end class EmptyNode < Node def initialize @value = nil @color = :BLACK end def empty? true end def size 0 end def each(&block) # intentionally blank end # returns new_root def insert(key, value) Node.new(key, value, self, self) end # returns value def retrieve(key) UNDEFINED end # returns [deleted_node, new_root, is_rebalance_needed] def delete(key) [self, self, false] end def dump_tree(io, indent = '') # intentionally blank end def dump_sexp # intentionally blank end end EMPTY = Node::EmptyNode.new.freeze end DEFAULT = Object.new attr_accessor :default attr_reader :default_proc def initialize(default = DEFAULT, &block) if block && default != DEFAULT raise ArgumentError, 'wrong number of arguments' end @root = Node::EMPTY @default = default @default_proc = block end def root @root end def empty? root == Node::EMPTY end def size root.size end alias length size def each(&block) if block_given? root.each(&block) self else Enumerator.new(root) end end alias each_pair each def each_key if block_given? root.each do |k, v| yield k end self else Enumerator.new(root, :each_key) end end def each_value if block_given? root.each do |k, v| yield v end self else Enumerator.new(root, :each_value) end end def keys root.keys end def values root.values end def clear @root = Node::EMPTY end def []=(key, value) @root = @root.insert(key, value) @root.set_root @root.check_height if $DEBUG end alias insert []= def key?(key) root.retrieve(key) != Node::UNDEFINED end alias has_key? key? def [](key) value = @root.retrieve(key) if value == Node::UNDEFINED default_value else value end end def delete(key) deleted, @root, rebalance = @root.delete(key) unless empty? @root.set_root @root.check_height if $DEBUG end deleted.value end def dump_tree(io = '') root.dump_tree(io) io << $/ io end def dump_sexp root.dump_sexp || '' end def to_hash inject({}) { |r, (k, v)| r[k] = v; r } end private def default_value if @default != DEFAULT @default elsif @default_proc @default_proc.call else nil end end end class ConcurrentRedBlackTree < RedBlackTree class ConcurrentNode < Node # direction: ~LEFT == RIGHT, ~RIGHT == LEFT LEFT = -1 RIGHT = 0 # @Overrides def insert(key, value) case key <=> @key when -1 dir = LEFT when 0 node = new_value(value) when 1 dir = RIGHT else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end if dir target = child(dir).insert(key, value) node = new_child(dir, target) if black? and child(~dir).black? and target.red? and !target.children_color?(:BLACK) node = node.rebalance_for_insert(dir) end end node.pullup_red end # @Overrides def retrieve(key) case key <=> @key when -1 @left.retrieve(key) when 0 @value when 1 @right.retrieve(key) else nil end end # @Overrides def delete(key) case key <=> @key when -1 dir = LEFT when 0 deleted = self node, rebalance = delete_node when 1 dir = RIGHT else raise TypeError, "cannot compare #{key} and #{@key} with <=>" end if dir deleted, target, rebalance = child(dir).delete(key) node = new_child(dir, target) if rebalance node, rebalance = node.rebalance_for_delete(dir) end end [deleted, node, rebalance] end protected def new_children(dir, node, other, color = @color) dir == LEFT ? ConcurrentNode.new(@key, @value, node, other, color) : ConcurrentNode.new(@key, @value, other, node, color) end def new_child(dir, node, color = @color) dir == LEFT ? ConcurrentNode.new(@key, @value, node, @right, color) : ConcurrentNode.new(@key, @value, @left, node, color) end def new_color(color) ConcurrentNode.new(@key, @value, @left, @right, color) end def new_value(value) ConcurrentNode.new(@key, value, @left, @right, @color) end def child(dir) dir == LEFT ? @left : @right end # @Overrides def delete_min if @left.empty? [self, *delete_node] else deleted, left, rebalance = @left.delete_min node = new_child(LEFT, left) if rebalance node, rebalance = node.rebalance_for_delete(LEFT) end [deleted, node, rebalance] end end # rebalance when the left/right sub-tree is 1 level lower than the right/left def rebalance_for_delete(dir) target = child(~dir) rebalance = false if black? if target.black? if target.children_color?(:BLACK) # make whole sub-tree 1 level lower and ask rebalance node = new_child(~dir, target.new_color(:RED)) rebalance = true else # move 1 black from the right to the left by single/double rotation node = balanced_rotate(dir) end else # flip this sub-tree into another type of 3-children node node = rotate(dir) # try to rebalance in sub-tree target, rebalance = node.child(dir).rebalance_for_delete(dir) raise 'should not happen' if rebalance node = node.new_children(dir, target, node.child(~dir)) end else # red if target.children_color?(:BLACK) # make right sub-tree 1 level lower node = new_child(~dir, target.new_color(@color), target.color) else # move 1 black from the right to the left by single/double rotation node = balanced_rotate(dir) end end [node, rebalance] end # move 1 black from the right/left to the left/right by single/double rotation def balanced_rotate(dir) target = child(~dir) if target.child(dir).red? and target.child(~dir).black? node = new_child(~dir, target.rotate(~dir)) else node = self end node = node.rotate(dir) node.new_children(dir, node.child(dir).new_color(:BLACK), node.child(~dir).new_color(:BLACK)) end # Right single rotation # (b a (D c E)) where D and E are RED --> (d (B a c) E) # # b d # / \ / \ # a D -> B E # / \ / \ # c E a c # # Left single rotation # (d (B A c) e) where A and B are RED --> (b A (D c e)) # # d b # / \ / \ # B e -> A D # / \ / \ # A c c e # def rotate(dir) new_root = child(~dir) node = new_child(~dir, new_root.child(dir), new_root.color) new_root.new_children(dir, node, new_root.child(~dir), @color) end # Pull up red nodes # (b (A C)) where A and C are RED --> (B (a c)) # # b B # / \ -> / \ # A C a c # # @Overrides def pullup_red if black? and @left.red? and @right.red? new_children(LEFT, @left.new_color(:BLACK), @right.new_color(:BLACK), :RED) else self end end # rebalance when the left/right sub-tree is 1 level higher than the right/left # move 1 black from the left to the right by single/double rotation # # precondition: self is black and @left/@right is red def rebalance_for_insert(dir) node = self if child(dir).child(~dir).red? node = new_child(dir, child(dir).rotate(dir)) end node.rotate(~dir) end private # @Overrides def delete_node rebalance = false if @left.empty? and @right.empty? # just remove this node and ask rebalance to the parent new_node = EMPTY_CONCURRENT if black? rebalance = true end elsif @left.empty? or @right.empty? # pick the single children new_node = @left.empty? ? @right : @left if black? # keep the color black raise 'should not happen' unless new_node.red? new_node = new_node.new_color(@color) else # just remove the red node end else # pick the minimum node from the right sub-tree and replace self with it deleted, right, rebalance = @right.delete_min new_node = deleted.new_children(LEFT, @left, right, @color) if rebalance new_node, rebalance = new_node.rebalance_for_delete(RIGHT) end end [new_node, rebalance] end class EmptyConcurrentNode < EmptyNode # @Overrides def insert(key, value) ConcurrentNode.new(key, value, self, self) end end EMPTY_CONCURRENT = ConcurrentNode::EmptyConcurrentNode.new.freeze end def initialize(default = DEFAULT, &block) super @root = Atomic.new(ConcurrentNode::EMPTY_CONCURRENT) end def root @root.get end def empty? root == ConcurrentNode::EMPTY_CONCURRENT end def clear @root.set(ConcurrentNode::EMPTY_CONCURRENT) end def []=(key, value) @root.update { |root| root = root.insert(key, value) root.set_root root.check_height if $DEBUG root } end alias insert []= def [](key) value = @root.get.retrieve(key) if value == Node::UNDEFINED default_value else value end end def delete(key) deleted = nil @root.update { |root| deleted, root, rebalance = root.delete(key) unless root == ConcurrentNode::EMPTY_CONCURRENT root.set_root root.check_height if $DEBUG end root } deleted.value end end