RB-Trees for Ruby - originally by OZAWA Takuma
C Ruby C++
Pull request Compare This branch is 46 commits ahead of skade:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
ext
test
.gitignore
ChangeLog
LICENSE
README.rdoc
Rakefile
rbtree.gemspec

README.rdoc

Ruby/RBTree

RBTree is a sorted associative collection that is implemented with Red-Black Tree. The elements of RBTree are ordered and its interface is the almost same as Hash, so simply you can consider RBTree sorted Hash.

Red-Black Tree is a kind of binary tree that automatically balances by itself when a node is inserted or deleted. Thus the complexity for insert, search and delete is O(log N) in expected and worst case. On the other hand the complexity of Hash is O(1). Because Hash is unordered the data structure is more effective than Red-Black Tree as an associative collection.

The elements of RBTree are sorted with natural ordering (by <=> method) of its keys or by a comparator(Proc) set by readjust method. It means all keys in RBTree should be comparable with each other. Or a comparator that takes two arguments of a key should return negative, 0, or positive depending on the first argument is less than, equal to, or greater than the second one.

The interface of RBTree is the almost same as Hash and there are a few methods to take advantage of the ordering:

  • lower_bound, upper_bound, bound

  • first, last

  • shift, pop

  • reverse_each

Note: while iterating RBTree (e.g. in a block of each method), it is not modifiable, or TypeError is thrown.

RBTree supoorts pretty printing using pp.

This library contains three classes. The two base classes are RBTree and MultiRBTree, which is a parent class of RBTree. RBTree does not allow duplications of keys but MultiRBTree does.

require "rbtree"

rbtree = RBTree["c", 10, "a", 20]
rbtree["b"] = 30
p rbtree["b"]              # => 30
rbtree.each do |k, v|
  p [k, v]
end                        # => ["a", 20] ["b", 30] ["c", 10]

mrbtree = MultiRBTree["c", 10, "a", 20, "e", 30, "a", 40]
p mrbtree.lower_bound("b") # => ["c", 10]
mrbtree.bound("a", "d") do |k, v|
  p [k, v]
end                        # => ["a", 20] ["a", 40] ["c", 10]

In addition, this library also defines a class RBTree::Element, which is useful for quick traversals of the tree:

require "rbtree"
rbtree = RBTree["c", 10, "a", 20]
first = rbtree.first_element
p first.key                # => "a"
p first.value              # => 20
p first.tree               # => #<RBTree: {"a"=>20, "c"=>10} ...
p first.next.key           # => "c"
p first.next.next          # => nil
last = rbtree.last_first
p last.key                # => "c"
p last.prev.key           # => "b"
p last.prev.prev          # => nil

The main use of this is to allow you to maintain “state” in a traversal of the tree. That is, you can do the following easily:

require "rbtree"
rbtree = RBTree[ ... ]
e = rbtree.fetch_element("q") # This fetches the element at key "q"
p e.key                   # => "q"  See!
saved_position = e
... go away and do other things for a long time ...
resume_at_element = saved_position
p resume_at_element.key   # => "q" It remembered where we were

Of course, you could do also do this using RBTree#fetch, or RBTree#[], but this will be significantly slower, because RBTree will do a binary search to find the element again when you resume.

You can also use RBTree::Elements instead of the “ruby-ish” iterators that RBTree provides (RBTree#each, etc.). There's no speed reason to do so, since the iterators are fast. The main reason to iterate using RBTree::Element#next etc. is because, unlike the RBTree iterators, RBTree::Elements do not complain when the underlying tree changes (with one exception – see below). So for instance, you can do:

require "rbtree"
rbtree = RBTree["c", 10, "a", 20]
first = rbtree.first_element
p first.key                # => "a"
p first.next.key           # => "c"
rbtree["b"] = 15
p first.next.key           # => "b"
b = rbtree.lower_bound_element("b")
p b.key                    # => "c"
d = rbtree.upper_bound_element("d")
p d.key                    # => "c"

The one exception is: the results are undefined if you use an RBTree::Element to refer to an element in the RBTree which has been deleted from the tree. It will most likely result in an error. So, don't do this:

require "rbtree"
rbtree = RBTree["c", 10, "a", 20]
first = rbtree.first_element
p first.key                # => "a"
rbtree.delete("a")
first.key                  # BOOM!

This is not to say that you can't delete an element while using RBTree::Elements – you can. Everything in the previous example would work just fine up to the last step. Just don't try to use the RBTree::Element to refer to the deleted element after it has been deleted.

Also available for use with RBTree::Element is the RBTree#add_element method:

rbtree = RBTree.new
element = rbtree.add_element('a', 'A')
element.key                                # => 'a'
element.value                              # => 'A'

This has a very similar function to RBTree#[], except that it can be used to keep track of a traversal from the insertion point, since it returns a RBTree::Element value that points at where the new node was inserted.

You can also delete elements directly:

rbtree = RBTree["c", 10, "a", 20]
first = rbtree.first_element
p first.key                # => "a"
first.delete
first = rbtree.first_element
p first.key                # => "c"

Requirement

  • Ruby 1.9.x

  • Ruby 2.0.x

Install

$ [sudo] gem install rbtree

Or grab the sourcecode from github.com/rleber/rbtree:

$ git clone git://github.com/rleber/rbtree

and then

$ rake compile
$ rake gem
$ [sudo] gem install pkg/rbtree-*.gem

Test

$ rake test

Incomplete Documentation

$ rake rdoc

or online documents at rbtree.rubyforge.org/.

License

MIT License. Copyright © 2002-2004, 2007, 2009-2010 OZAWA Takuma.

dict.c and dict.h are modified copies that are originally in Kazlib written by Kaz Kylheku. Copyright is held by Kaz Kylheku, see dict.c and dict.h for the license. The web page of Kazlib is at users.footprints.net/~kaz/kazlib.html.

Support

Bug fixes, suggestions and other feedbacks are welcomed. Please mail me at burningdowntheopera at yahoo dot co dot jp.

Links