[DRE-commits] [ruby-avl-tree] 01/05: Imported Upstream version 1.1.3
Tim Potter
tpot-guest at moszumanska.debian.org
Tue Aug 5 00:41:54 UTC 2014
This is an automated email from the git hooks/post-receive script.
tpot-guest pushed a commit to branch master
in repository ruby-avl-tree.
commit 6072e872dbf14f39bbc3b846e55e642a01e3e026
Author: Tim Potter <tpot at hp.com>
Date: Fri Aug 1 13:29:35 2014 +1000
Imported Upstream version 1.1.3
---
README | 16 ++
bench/bench.rb | 51 ++++
bench/bench_element_size.rb | 53 ++++
bench/profile.rb | 13 +
lib/avl_tree.rb | 410 +++++++++++++++++++++++++++++
lib/red_black_tree.rb | 552 ++++++++++++++++++++++++++++++++++++++
metadata.yml | 59 +++++
test/helper.rb | 8 +
test/test_avl_tree.rb | 479 +++++++++++++++++++++++++++++++++
test/test_red_black_tree.rb | 629 ++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 2270 insertions(+)
diff --git a/README b/README
new file mode 100644
index 0000000..7ef00b4
--- /dev/null
+++ b/README
@@ -0,0 +1,16 @@
+avl_tree - AVL tree and Red-black tree in Ruby
+Copyright (C) 2012 Hiroshi Nakamura <nahi at ruby-lang.org>
+
+
+== Author
+
+Name:: Hiroshi Nakamura
+E-mail:: nahi at 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.
diff --git a/bench/bench.rb b/bench/bench.rb
new file mode 100644
index 0000000..3db2b9d
--- /dev/null
+++ b/bench/bench.rb
@@ -0,0 +1,51 @@
+require '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
diff --git a/bench/bench_element_size.rb b/bench/bench_element_size.rb
new file mode 100644
index 0000000..f424f08
--- /dev/null
+++ b/bench/bench_element_size.rb
@@ -0,0 +1,53 @@
+require '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
diff --git a/bench/profile.rb b/bench/profile.rb
new file mode 100644
index 0000000..dc03497
--- /dev/null
+++ b/bench/profile.rb
@@ -0,0 +1,13 @@
+require 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
diff --git a/lib/avl_tree.rb b/lib/avl_tree.rb
new file mode 100644
index 0000000..c9b8af3
--- /dev/null
+++ b/lib/avl_tree.rb
@@ -0,0 +1,410 @@
+class 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 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
diff --git a/lib/red_black_tree.rb b/lib/red_black_tree.rb
new file mode 100644
index 0000000..ee5e0ce
--- /dev/null
+++ b/lib/red_black_tree.rb
@@ -0,0 +1,552 @@
+class RedBlackTree
+ include Enumerable
+
+ class Node
+ UNDEFINED = Object.new
+
+ attr_reader :key, :value, :color
+ attr_reader :left, :right
+
+ def initialize(key, value)
+ @key, @value = key, value
+ @left = @right = EMPTY
+ # new node is added as RED
+ @color = :RED
+ 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_both_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_both_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_self
+ 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.empty? ? 0 : @left.check_height
+ rh = @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_both_black?
+ @right.black? and @left.black?
+ 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 node_flip(other)
+ @left, other.left = other.left, @left
+ @right, other.right = other.right, @right
+ color_flip(other)
+ end
+
+ def delete_min
+ if @left.empty?
+ [self, *delete_self]
+ 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_both_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_both_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_both_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_both_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 @left.red? and @right.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_self
+ 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
+ new_root, @right, rebalance = @right.delete_min
+ new_root.node_flip(self)
+ 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)
+ 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 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
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..3935869
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,59 @@
+--- !ruby/object:Gem::Specification
+name: avl_tree
+version: !ruby/object:Gem::Version
+ version: 1.1.3
+ prerelease:
+platform: ruby
+authors:
+- Hiroshi Nakamura
+autorequire:
+bindir: bin
+cert_chain: []
+date: 2012-05-09 00:00:00.000000000 Z
+dependencies: []
+description:
+email: nahi at ruby-lang.org
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- lib/red_black_tree.rb
+- lib/avl_tree.rb
+- bench/bench_element_size.rb
+- bench/profile.rb
+- bench/bench.rb
+- test/test_red_black_tree.rb
+- test/helper.rb
+- test/test_avl_tree.rb
+- README
+homepage: http://github.com/nahi/avl_tree
+licenses: []
+post_install_message:
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ segments:
+ - 0
+ hash: -4094052252433988634
+required_rubygems_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ segments:
+ - 0
+ hash: -4094052252433988634
+requirements: []
+rubyforge_project:
+rubygems_version: 1.8.23
+signing_key:
+specification_version: 3
+summary: AVL tree and Red-black tree in Ruby
+test_files: []
diff --git a/test/helper.rb b/test/helper.rb
new file mode 100644
index 0000000..cee8063
--- /dev/null
+++ b/test/helper.rb
@@ -0,0 +1,8 @@
+begin
+ require 'simplecov'
+ SimpleCov.start
+rescue LoadError
+end
+require "test/unit"
+require "avl_tree"
+require "red_black_tree"
diff --git a/test/test_avl_tree.rb b/test/test_avl_tree.rb
new file mode 100644
index 0000000..462768f
--- /dev/null
+++ b/test/test_avl_tree.rb
@@ -0,0 +1,479 @@
+# -*- 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
+
+ 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
diff --git a/test/test_red_black_tree.rb b/test/test_red_black_tree.rb
new file mode 100644
index 0000000..6f60377
--- /dev/null
+++ b/test/test_red_black_tree.rb
@@ -0,0 +1,629 @@
+# -*- encoding: utf-8 -*-
+require File.expand_path('./helper', File.dirname(__FILE__))
+
+class TestRedBlackTree < Test::Unit::TestCase
+ def __test_random
+ h = RedBlackTree.new
+ 10000.times do |idx|
+ key = rand(100)
+ h[key] = key
+ key = rand(100)
+ h.delete(key)
+ end
+ end
+
+ def test_tree_rotate_RR
+ h = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.new
+ 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 = RedBlackTree.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 '(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 = RedBlackTree.new
+ h['abc'] = 1
+ assert_equal nil, h['def']
+ end
+
+ def test_empty
+ h = RedBlackTree.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 = RedBlackTree.new
+ h['abc'] = 1
+ assert_equal 1, h['abc']
+ end
+
+ def test_aref_double
+ h = RedBlackTree.new
+ h['abc'] = 1
+ h['def'] = 2
+ assert_equal 1, h['abc']
+ assert_equal 2, h['def']
+ end
+
+ def test_aset_override
+ h = RedBlackTree.new
+ h['abc'] = 1
+ h['abc'] = 2
+ assert_equal 2, h['abc']
+ end
+
+ def test_split
+ h = RedBlackTree.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 = RedBlackTree.new
+ h['ab'] = 1
+ h['a'] = 2
+ assert_equal 1, h['ab']
+ assert_equal 2, h['a']
+ end
+
+ def test_push
+ h = RedBlackTree.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 = RedBlackTree.new
+ h['a'] = 1
+ assert_raise(TypeError) do
+ h[3.3] = 2
+ end
+ assert_nil h[3.3]
+ end
+
+ def test_delete_leaf
+ h = RedBlackTree.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 = RedBlackTree.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_single_rotation_right
+ h = RedBlackTree.new
+ 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 = RedBlackTree.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 '(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 = RedBlackTree.new
+ 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 = RedBlackTree.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 '(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 = RedBlackTree.new
+ 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 = RedBlackTree.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 = RedBlackTree.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_right
+ h = RedBlackTree.new
+ 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 = RedBlackTree.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 = RedBlackTree.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_balanced_rotate_left
+ h = RedBlackTree.new
+ 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 = RedBlackTree.new
+ 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 = RedBlackTree.new
+ h['a'] = 1
+ h['abc'] = 2
+ h['bb'] = 3
+
+ assert_raise(TypeError) do
+ h.delete(3.3)
+ end
+ end
+
+ def test_each
+ h = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.new
+ h[5] = 1
+ assert_equal 1, h[5]
+ assert_nil h["5"]
+ end
+
+ def test_key?
+ h = RedBlackTree.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
+ RedBlackTree.new('both') { :not_allowed }
+ end
+
+ h = RedBlackTree.new('abc')
+ assert_equal 'abc', h['foo']
+ assert_equal 'abc', h['bar']
+ assert h['baz'].object_id == h['qux'].object_id
+
+ h = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.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 = RedBlackTree.new
+ h[1.3] = 'a'
+ h[4.3] = 'b'
+
+ assert_equal [1.3, 'a' ], h.first
+ end
+
+ def test_values_for_empty_tree
+ h = RedBlackTree.new
+
+ assert_equal [], h.values
+ end
+
+ if RUBY_VERSION >= '1.9.0'
+ # In contrast to RadixTree, RedBlackTree just uses String#<=> as-is
+ def test_encoding
+ h = RedBlackTree.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
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-avl-tree.git
More information about the Pkg-ruby-extras-commits
mailing list