[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