Mercurial > repos > jjohnson > crest
diff Tree/Interval.pm @ 0:acc8d8bfeb9a
Uploaded
author | jjohnson |
---|---|
date | Wed, 08 Feb 2012 16:59:24 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Tree/Interval.pm Wed Feb 08 16:59:24 2012 -0500 @@ -0,0 +1,741 @@ +package Tree::Interval; + +use strict; +use Carp; + +use Tree::Interval::Node qw[set_color color_of parent_of left_of right_of]; +use Tree::Interval::Node::Constants; +use Tree::DAG_Node; +use vars qw( $VERSION @EXPORT_OK ); +$VERSION = 0.1; + +require Exporter; +*import = \&Exporter::import; +@EXPORT_OK = qw[LUEQUAL LUGTEQ LULTEQ LUGREAT LULESS LUNEXT LUPREV]; + +use enum qw{ LUEQUAL LUGTEQ LULTEQ LUGREAT LULESS LUNEXT LUPREV }; + +# object slots +use enum qw{ ROOT CMP SIZE }; + +sub new { + my ($class, $cmp) = @_; + my $obj = []; + $obj->[SIZE] = 0; + if($cmp) { + ref $cmp eq 'CODE' + or croak('Invalid arg: codref expected'); + $obj->[CMP] = $cmp; + } + else { + # default compare + $obj->[CMP] = sub { $_[0]->[0] <=> $_[1]->[0] || $_[0]->[1] <=> $_[1]->[1]}; + } + return bless $obj => $class; +} + + +sub DESTROY { $_[0]->[ROOT]->DESTROY if $_[0]->[ROOT] } + +sub root { $_[0]->[ROOT] } +sub size { $_[0]->[SIZE] } + +sub left_most { + my $self = shift; + return undef unless $self->[ROOT]; + return $self->[ROOT]->left_most; +} + +sub right_most { + my $self = shift; + return undef unless $self->[ROOT]; + return $self->[ROOT]->right_most; +} + +# return all the intervals intersect with the interval +sub intersect { + my $self = shift; + my $interval = shift; + return my @tmp unless $self->[ROOT]; + return $self->[ROOT]->intersect($interval); +} + +sub lookup { + my $self = shift; + my $key = shift; + defined $key + or croak("Can't use undefined value as key"); + my $mode = shift || LUEQUAL; + my $cmp = $self->[CMP]; + + my $y; + my $x = $self->[ROOT] + or return; + my $next_child; + while($x) { + $y = $x; + if($cmp ? $cmp->($key, $x->[_KEY]) == 0 + : $key eq $x->[_KEY]) { + # found it! + if($mode == LUGREAT || $mode == LUNEXT) { + $x = $x->successor; + } + elsif($mode == LULESS || $mode == LUPREV) { + $x = $x->predecessor; + } + return wantarray + ? ($x->[_VAL], $x) + : $x->[_VAL]; + } + if($cmp ? $cmp->($key, $x->[_KEY]) < 0 + : $key lt $x->[_KEY]) { + $next_child = _LEFT; + } + else { + $next_child = _RIGHT; + } + $x = $x->[$next_child]; + } + # Didn't find it :( + if($mode == LUGTEQ || $mode == LUGREAT) { + if($next_child == _LEFT) { + return wantarray ? ($y->[_VAL], $y) : $y->[_VAL]; + } + else { + my $next = $y->successor + or return; + return wantarray ? ($next->[_VAL], $next) : $next->[_VAL]; + } + } + elsif($mode == LULTEQ || $mode == LULESS) { + if($next_child == _RIGHT) { + return wantarray ? ($y->[_VAL], $y) : $y->[_VAL]; + } + else { + my $next = $y->predecessor + or return; + return wantarray ? ($next->[_VAL], $next) : $next->[_VAL]; + } + } + return; +} + +sub insert { + my $self = shift; + my $key_or_node = shift; + defined $key_or_node + or croak("Can't use undefined value as key or node"); + my $val = shift; + + my $cmp = $self->[CMP]; + my $z = (ref $key_or_node eq 'Tree::Interval::Node') + ? $key_or_node + : Tree::Interval::Node->new($key_or_node => $val); + + my $y; + my $x = $self->[ROOT]; + while($x) { + $y = $x; + # Handle case of inserting node with duplicate key. + if($cmp ? $cmp->($z->[_KEY], $x->[_KEY]) == 0 + : $z->[_KEY] eq $x->[_KEY]) + { + warn "The same key (range) is already in the tree + it will be replaced!"; + my $old_val = $x->[_VAL]; + $x->[_VAL] = $z->[_VAL]; + return $old_val; + } + + if($cmp ? $cmp->($z->[_KEY], $x->[_KEY]) < 0 + : $z->[_KEY] lt $x->[_KEY]) + { + $x = $x->[_LEFT]; + } + else { + $x = $x->[_RIGHT]; + } + } + # insert new node + $z->[_PARENT] = $y; + if(not defined $y) { + $self->[ROOT] = $z; + } + else { + if($cmp ? $cmp->($z->[_KEY], $y->[_KEY]) < 0 + : $z->[_KEY] lt $y->[_KEY]) + { + $y->[_LEFT] = $z; + } + else { + $y->[_RIGHT] = $z; + } + } + _update_max($z); + $self->_fix_after_insertion($z); + $self->[SIZE]++; +} + +sub _fix_after_insertion { + my $self = shift; + my $x = shift or croak('Missing arg: node'); + + $x->[_COLOR] = RED; + while($x != $self->[ROOT] && $x->[_PARENT][_COLOR] == RED) { + my ($child, $rotate1, $rotate2); + if(($x->[_PARENT] || 0) == ($x->[_PARENT][_PARENT][_LEFT] || 0)) { + ($child, $rotate1, $rotate2) = (_RIGHT, '_left_rotate', '_right_rotate'); + } + else { + ($child, $rotate1, $rotate2) = (_LEFT, '_right_rotate', '_left_rotate'); + } + my $y = $x->[_PARENT][_PARENT][$child]; + + if($y && $y->[_COLOR] == RED) { + $x->[_PARENT][_COLOR] = BLACK; + $y->[_COLOR] = BLACK; + $x->[_PARENT][_PARENT][_COLOR] = RED; + $x = $x->[_PARENT][_PARENT]; + } + else { + if($x == ($x->[_PARENT][$child] || 0)) { + $x = $x->[_PARENT]; + $self->$rotate1($x); + } + $x->[_PARENT][_COLOR] = BLACK; + $x->[_PARENT][_PARENT][_COLOR] = RED; + $self->$rotate2($x->[_PARENT][_PARENT]); + } + } + $self->[ROOT][_COLOR] = BLACK; +} + +sub delete { + my ($self, $key_or_node) = @_; + defined $key_or_node + or croak("Can't use undefined value as key or node"); + + my $z = (ref $key_or_node eq 'Tree::Interval::Node') + ? $key_or_node + : ($self->lookup($key_or_node))[1]; + return unless $z; + + my $y; + if($z->[_LEFT] && $z->[_RIGHT]) { + # (Notes kindly provided by Christopher Gurnee) + # When deleting a node 'z' which has two children from a binary search tree, the + # typical algorithm is to delete the successor node 'y' instead (which is + # guaranteed to have at most one child), and then to overwrite the key/values of + # node 'z' (which is still in the tree) with the key/values (which we don't want + # to lose) from the now-deleted successor node 'y'. + + # Since we need to return the deleted item, it's not good enough to overwrite the + # key/values of node 'z' with those of node 'y'. Instead we swap them so we can + # return the deleted values. + + $y = $z->predecessor; + ($z->[_KEY], $y->[_KEY]) = ($y->[_KEY], $z->[_KEY]); + ($z->[_VAL], $y->[_VAL]) = ($y->[_VAL], $z->[_VAL]); + ($z->[_INTERVAL], $y->[_INTERVAL]) = ($y->[_INTERVAL], $z->[_INTERVAL]); + } + else { + $y = $z; + } + + # splice out $y + my $x = $y->[_LEFT] || $y->[_RIGHT]; + if(defined $x) { + $x->[_PARENT] = $y->[_PARENT]; + if(! defined $y->[_PARENT]) { + $self->[ROOT] = $x; + } + elsif($y == $y->[_PARENT][_LEFT]) { + $y->[_PARENT][_LEFT] = $x; + } + else { + $y->[_PARENT][_RIGHT] = $x; + } + # Null out links so they are OK to use by _fix_after_deletion + delete @{$y}[_PARENT, _LEFT, _RIGHT]; + _update_max($x); + # Fix replacement + if($y->[_COLOR] == BLACK) { + $self->_fix_after_deletion($x); + } + } + elsif(! defined $y->[_PARENT]) { + # return if we are the only node + delete $self->[ROOT]; + } + else { + # No children. Use self as phantom replacement and unlink + if($y->[_COLOR] == BLACK) { + $self->_fix_after_deletion($y); + } + if(defined $y->[_PARENT]) { + no warnings 'uninitialized'; + if($y == $y->[_PARENT][_LEFT]) { + delete $y->[_PARENT][_LEFT]; + } + elsif($y == $y->[_PARENT][_RIGHT]) { + delete $y->[_PARENT][_RIGHT]; + } + my $tmp = $y->[_PARENT]; + delete $y->[_PARENT]; + _update_max($tmp); + } + } + $self->[SIZE]--; + return $y; +} + +sub _fix_after_deletion { + my $self = shift; + my $x = shift or croak('Missing arg: node'); + + while($x != $self->[ROOT] && color_of($x) == BLACK) { + my ($child1, $child2, $rotate1, $rotate2); + no warnings 'uninitialized'; + if($x == left_of(parent_of($x))) { + ($child1, $child2, $rotate1, $rotate2) = + (\&right_of, \&left_of, '_left_rotate', '_right_rotate'); + } + else { + ($child1, $child2, $rotate1, $rotate2) = + (\&left_of, \&right_of, '_right_rotate', '_left_rotate'); + } + use warnings; + + my $w = $child1->(parent_of($x)); + if(color_of($w) == RED) { + set_color($w, BLACK); + set_color(parent_of($x), RED); + $self->$rotate1(parent_of($x)); + $w = right_of(parent_of($x)); + } + if(color_of($child2->($w)) == BLACK && + color_of($child1->($w)) == BLACK) { + set_color($w, RED); + $x = parent_of($x); + } + else { + if(color_of($child1->($w)) == BLACK) { + set_color($child2->($w), BLACK); + set_color($w, RED); + $self->$rotate2($w); + $w = $child1->(parent_of($x)); + } + set_color($w, color_of(parent_of($x))); + set_color(parent_of($x), BLACK); + set_color($child1->($w), BLACK); + $self->$rotate1(parent_of($x)); + $x = $self->[ROOT]; + } + } + set_color($x, BLACK); +} + + +sub _max3 { + my ($a, $b, $c) = @_; + my $min = ($a || $b || $c) - 1; + $a = $a || $min; + $b = $b || $min; + $c = $c || $min; + return $a if($a >= $b && $a >= $c); + return $b if($b >= $a && $b >= $c); + return $c if($c >= $a && $c >= $b); +} + +sub _max { + my $x = shift; + my $tmp = _max3($x->[_INTERVAL][1], + $x->[_LEFT] ? $x->[_LEFT][_MAX] : undef, + $x->[_RIGHT] ? $x->[_RIGHT][_MAX] : undef); + $x->[_MAX] = $tmp; + return $tmp; +} + +sub _update_max { + my $x = shift; + #update the max field for each parent node + _max($x); + my $k = $x->[_PARENT]; + while($k) { + my $tmp = $k->[_MAX]; + _max($k); + last if($tmp == $k->[_MAX]); # no need to update further + $k = $k->[_PARENT]; + } +} + +sub _left_rotate { + my $self = shift; + my $x = shift or croak('Missing arg: node'); + + my $y = $x->[_RIGHT] + or return; + $x->[_RIGHT] = $y->[_LEFT]; + if($y->[_LEFT]) { + $y->[_LEFT]->[_PARENT] = $x; + } + $y->[_PARENT] = $x->[_PARENT]; + if(not defined $x->[_PARENT]) { + $self->[ROOT] = $y; + } + else { + $x == $x->[_PARENT]->[_LEFT] + ? $x->[_PARENT]->[_LEFT] = $y + : $x->[_PARENT]->[_RIGHT] = $y; + } + $y->[_LEFT] = $x; + $x->[_PARENT] = $y; + #update max + _max($x); _max($y); +} + +sub _right_rotate { + my $self = shift; + my $y = shift or croak('Missing arg: node'); + + my $x = $y->[_LEFT] + or return; + $y->[_LEFT] = $x->[_RIGHT]; + if($x->[_RIGHT]) { + $x->[_RIGHT]->[_PARENT] = $y + } + $x->[_PARENT] = $y->[_PARENT]; + if(not defined $y->[_PARENT]) { + $self->[ROOT] = $x; + } + else { + $y == $y->[_PARENT]->[_RIGHT] + ? $y->[_PARENT]->[_RIGHT] = $x + : $y->[_PARENT]->[_LEFT] = $x; + } + $x->[_RIGHT] = $y; + $y->[_PARENT] = $x; + _max($y); _max($x); +} + +1; + +# Magic true value required at end of module +__END__ + +=head1 NAME + +Tree::RB - Perl implementation of the Red/Black tree, a type of balanced binary search tree. + + +=head1 VERSION + +This document describes Tree::RB version 0.1 + + +=head1 SYNOPSIS + + use Tree::RB; + + my $tree = Tree::RB->new; + $tree->put('France' => 'Paris'); + $tree->put('England' => 'London'); + $tree->put('Hungary' => 'Budapest'); + $tree->put('Ireland' => 'Dublin'); + $tree->put('Egypt' => 'Cairo'); + $tree->put('Germany' => 'Berlin'); + + $tree->put('Alaska' => 'Anchorage'); # D'oh! + $tree->delete('Alaska'); + + print $tree->get('Ireland'); # 'Dublin' + + print $tree->min->key; # 'Egypt' + print $tree->max->key; # 'Ireland' + print $tree->size; # 6 + + # print items, ordered by key + my $it = $tree->iter; + + while(my $node = $it->next) { + sprintf "key = %s, value = %s\n", $node->key, $node->val; + } + + # print items in reverse order + $it = $tree->rev_iter; + + while(my $node = $it->next) { + sprintf "key = %s, value = %s\n", $node->key, $node->val; + } + + # Hash interface + tie my %capital, 'Tree::RB'; + + # or do this to store items in descending order + tie my %capital, 'Tree::RB', sub { $_[1] cmp $_[0] }; + + $capital{'France'} = 'Paris'; + $capital{'England'} = 'London'; + $capital{'Hungary'} = 'Budapest'; + $capital{'Ireland'} = 'Dublin'; + $capital{'Egypt'} = 'Cairo'; + $capital{'Germany'} = 'Berlin'; + + # print items in order + while(my ($key, $val) = each %capital) { + printf "key = $key, value = $val\n"; + } + +=head1 DESCRIPTION + +This is a Perl implementation of the Red/Black tree, a type of balanced binary search tree. + +A tied hash interface is also provided to allow ordered hashes to be used. + +See the Wikipedia article at L<http://en.wikipedia.org/wiki/Red-black_tree> for more on Red/Black trees. + + +=head1 INTERFACE + +=head2 new([CODEREF]) + +Creates and returns a new tree. If a reference to a subroutine is passed to +new(), the subroutine will be used to override the tree's default lexical +ordering and provide a user a defined ordering. + +This subroutine should be just like a comparator subroutine used with L<sort>, +except that it doesn't do the $a, $b trick. + +For example, to get a case insensitive ordering + + my $tree = Tree::RB->new(sub { lc $_[0] cmp lc $_[1]}); + $tree->put('Wall' => 'Larry'); + $tree->put('Smith' => 'Agent'); + $tree->put('mouse' => 'micky'); + $tree->put('duck' => 'donald'); + + my $it = $tree->iter; + + while(my $node = $it->next) { + sprintf "key = %s, value = %s\n", $node->key, $node->val; + } + +=head2 resort(CODEREF) + +Changes the ordering of nodes within the tree. The new ordering is +specified by a comparator subroutine which must be passed to resort(). + +See L</new> for further information about the comparator. + +=head2 size() + +Returns the number of nodes in the tree. + +=head2 root() + +Returns the root node of the tree. This will either be undef +if no nodes have been added to the tree, or a L<Tree::RB::Node> object. +See the L<Tree::RB::Node> manual page for details on the Node object. + +=head2 min() + +Returns the node with the minimal key. + +=head2 max() + +Returns the node with the maximal key. + +=head2 lookup(KEY, [MODE]) + +When called in scalar context, lookup(KEY) returns the value +associated with KEY. + +When called in list context, lookup(KEY) returns a list whose first +element is the value associated with KEY, and whose second element +is the node containing the key/value. + +An optional MODE parameter can be passed to lookup() to influence +which key is returned. + +The values of MODE are constants that are exported on demand by +Tree::RB + + use Tree::RB qw[LUEQUAL LUGTEQ LULTEQ LUGREAT LULESS LUNEXT LUPREV]; + +=over + +=item LUEQUAL + +Returns node exactly matching the key. + +=item LUGTEQ + +Returns the node exactly matching the specified key, +if this is not found then the next node that is greater than the specified key is returned. + +=item LULTEQ + +Returns the node exactly matching the specified key, +if this is not found then the next node that is less than the specified key is returned. + +=item LUGREAT + +Returns the node that is just greater than the specified key - not equal to. +This mode is similar to LUNEXT except that the specified key need not exist in the tree. + +=item LULESS + +Returns the node that is just less than the specified key - not equal to. +This mode is similar to LUPREV except that the specified key need not exist in the tree. + +=item LUNEXT + +Looks for the key specified, if not found returns C<undef>. +If the node is found returns the next node that is greater than +the one found (or C<undef> if there is no next node). + +This can be used to step through the tree in order. + +=item LUPREV + +Looks for the key specified, if not found returns C<undef>. +If the node is found returns the previous node that is less than +the one found (or C<undef> if there is no previous node). + +This can be used to step through the tree in reverse order. + +=back + +=head2 get(KEY) + +get() is an alias for lookup(). + +=head2 iter([KEY]) + +Returns an iterator object that can be used to traverse the tree in order. + +The iterator object supports a 'next' method that returns the next node in the +tree or undef if all of the nodes have been visited. + +See the synopsis for an example. + +If a key is supplied, the iterator returned will traverse the tree in order starting from +the node with key greater than or equal to the specified key. + + $it = $tree->iter('France'); + my $node = $it->next; + print $node->key; # -> 'France' + +=head2 rev_iter([KEY]) + +Returns an iterator object that can be used to traverse the tree in reverse order. + +If a key is supplied, the iterator returned will traverse the tree in order starting from +the node with key less than or equal to the specified key. + + $it = $tree->rev_iter('France'); + my $node = $it->next; + print $node->key; # -> 'England' + +=head2 hseek(KEY, [{-reverse => 1|0}]) + +For tied hashes, determines the next entry to be returned by each. + + tie my %capital, 'Tree::RB'; + + $capital{'France'} = 'Paris'; + $capital{'England'} = 'London'; + $capital{'Hungary'} = 'Budapest'; + $capital{'Ireland'} = 'Dublin'; + $capital{'Egypt'} = 'Cairo'; + $capital{'Germany'} = 'Berlin'; + tied(%capital)->hseek('Germany'); + + ($key, $val) = each %capital; + print "$key, $val"; # -> Germany, Berlin + +The direction of iteration can be reversed by passing a hashref with key '-reverse' and value 1 +to hseek after or instead of KEY, e.g. to iterate over the hash in reverse order: + + tied(%capital)->hseek({-reverse => 1}); + $key = each %capital; + print $key; # -> Ireland + +The following calls are equivalent + + tied(%capital)->hseek('Germany', {-reverse => 1}); + tied(%capital)->hseek({-key => 'Germany', -reverse => 1}); + +=head2 put(KEY, VALUE) + +Adds a new node to the tree. + +The first argument is the key of the node, the second is its value. + +If a node with that key already exists, its value is replaced with +the given value and the old value is returned. Otherwise, undef is returned. + +=head2 delete(KEY) + +If the tree has a node with the specified key, that node is +deleted from the tree and returned, otherwise C<undef> is returned. + + +=head1 DEPENDENCIES + +L<enum> + + +=head1 INCOMPATIBILITIES + +None reported. + + +=head1 BUGS AND LIMITATIONS + +Please report any bugs or feature requests to +C<bug-tree-rb@rt.cpan.org>, or through the web interface at +L<http://rt.cpan.org>. + + +=head1 AUTHOR + +Arun Prasad C<< <arunbear@cpan.org> >> + +Some documentation has been borrowed from Benjamin Holzman's L<Tree::RedBlack> +and Damian Ivereigh's libredblack (L<http://libredblack.sourceforge.net/>). + +=head1 ACKNOWLEDGEMENTS + +Thanks for bug reports go to Anton Petrusevich, Wes Thompson and Christopher Gurnee. + +=head1 LICENCE AND COPYRIGHT + +Copyright (c) 2007, Arun Prasad C<< <arunbear@cpan.org> >>. All rights reserved. + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. See L<perlartistic>. + + +=head1 DISCLAIMER OF WARRANTY + +BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH +YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR, OR CORRECTION. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES.