=head1 NAME multi_tree.pm -- a multi node tree object. Most useful for modeling heirarchial data structures. =head1 SYNOPSIS use multi_tree; my $tree = new multi_tree; my $handle = new multi_tree::handle($tree); $handle->set_key("top"); $handle->set_key("level"); $handle->add_child("child","1"); $handle->add_child("child","2"); $handle->first(); $handle->down(); $handle->add_child("grandchild","1-1"); $handle->up(); $handle->last(); $handle->down(); $handle->add_child("grandchild","2-1"); $handle->up(); $handle->top(); &dump_tree($handle); sub dump_tree { ++$depth; my $handle = shift; my $lead = ' ' x ($depth*2); my($key,$val); ($key,$val) = $handle->get_data(); print $lead, "key: $key\n"; print $lead, "val: $val\n"; print $lead, "depth: $depth\n"; my $i; for( $i = 0; $i < scalar($handle->children); ++$i ) { $handle->down($i); &dump_tree($handle); $handle->up(); } --$depth; } =head1 DESCRIPTION multi_tree, multi_tree::node, and multi_tree::handle are objects modeled after C++ classes that I had written to help me model heirarchical information as datastructures (such as the relationships between tables in an RDBMS). The tree is basicly a list of lists type data structure, where each node has a key, a value, and a list of children. All operations perserve the order of the child nodes. =head2 Creating a Tree The syntax of creating a handle based on a tree lets you have multiple handles into a single tree without having to copy the tree. You have to use a handle to do anything at all to the tree. When you first construct a tree, it will have a single empty node. When you construct a handle into that tree, it will set the top node in the tree as it's current node. my $tree = new multi_tree; my $handle = new multi_tree::handle($tree); =head2 Using a Handle to Manipulate the Tree At this point, you can set the key/value in the top node, or start adding child nodes. $handle->set_key("blah"); $handle->set_value("foo"); $handle->add_child("quz","baz"); # or $handle->add_child(); multi_tree::handle::add_child can take 3 paramters -- a key, a value, and a position. The key and value will set the key/value of the child on construction. If pos is passed, the new child will be inserted into the list of children. To move the handle so it points at a child (so you can start manipulating that child), there are a series of methods to call: $handle->first(); # sets the current child to the first in the list $handle->next(); # sets the next, or first if there was no next $handle->prev(); # sets the previous, or last if there was no next $handle->last(); # sets to the last child $handle->down(); # positions the handle's current node to the current child To move back up, you can call the method up: $handle->up(); # moves to this node's parent up() will fail if the current node has no parent node. Most of the member functions return either undef to indicate failure, or some other value to indicate success. =head2 $multi_tree::debug If set to a true value, it enables debugging output in the code. This will likely be removed in future versions as the code becomes more stable. =head1 SEE ALSO Books on data structures! =head1 AUTHORS Kyle R. Burton mortis@voicenet.com =head1 BUGS - There is currently no way to remove a child node. - Not all the methods are tested, this is the first release and only very minimal testing has been done. - This documentation pretty much sucks. =cut ################################################################################ package multi_tree; require 5.004; $VERSION = "0.9.0"; @ISA = (); use Carp; sub new { my $self = {}; bless $self, shift; $self->{'top'} = multi_tree::node->new(); return $self; } ################################################################################ package multi_tree::node; use Carp; sub new { my $self = {}; bless $self, shift; my $node = shift; if( ref($node) eq "multi_tree::node" ) { # become a copy of that node... $self->{'parent'} = $node->parent; $self->{'children'} = [$node->children]; $self->{'key'} = $node->key; $self->{'value'} = $node->value; } else { my($key,$value); $key = $node; $value = shift; print "[new] key,val = $key,$value\n" if $multi_tree::debug; $self->{'children'} = []; $self->{'parent'} = undef; $self->{'key'} = $key || undef; $self->{'value'} = $value || undef; } return $self; } sub key { my $self = shift; my $key = shift; if($key) { print "[key] setting key: $key on $self\n" if $multi_tree::debug; $self->{'key'} = $key; } return $self->{'key'}; } sub value { my $self = shift; my $value = shift; if( defined $value ) { print "[value] setting value: $value on $self\n" if $multi_tree::debug; $self->{'value'} = $value; } return $self->{'value'}; } sub clear_key { my $self = shift; undef $self->{'key'}; } sub clear_value { my $self = shift; undef $self->{'value'}; } sub children { my $self = shift; return $self->{'children'}; } sub parent { my $self = shift; return $self->{'parent'}; } sub dump { my $self = shift; print "[dump] key: ", $self->{'key'}, "\n"; print "[dump] val: ", $self->{'value'}, "\n"; print "[dump] parent: ", $self->{'parent'}, "\n"; print "[dump] children: ", $self->{'children'}, "\n"; } ################################################################################ package multi_tree::handle; use Carp; sub new { my $self = {}; bless $self, shift; my $tree = shift; print "ref(tree) is: ", ref($tree), "\n" if $multi_tree::debug; unless( ref($tree) eq "multi_tree" ) { confess "Error, invalid multi_tree refrence: $tree\n"; } $self->{'tree'} = $tree; $self->{'curr_pos'} = undef; $self->{'curr_node'} = $tree->{'top'}; $self->{'curr_child'} = undef; return $self; } sub get_data { my $self = shift; my $node = $self->{'curr_node'}; return($node->key,$node->value); } sub get_key { my $self = shift; my $node = $self->{'curr_node'}; my $key = $node->key(); print "[get_key] getting from $node : $key\n" if $multi_tree::debug; return $key; } sub set_key { my $self = shift; my $key = shift; my $node = $self->{'curr_node'}; print "[set_key] setting key \"$key\" on: $node\n" if $multi_tree::debug; return $node->key($key); } sub get_value { my $self = shift; my $node = $self->{'curr_node'}; my $value = $node->value(); print "[get_value] getting from $node : $value\n" if $multi_tree::debug; return $value; } sub set_value { my $self = shift; my $value = shift; my $node = $self->{'curr_node'}; print "[set_value] setting value \"$value\" on: $node\n" if $multi_tree::debug; return $node->value($value); } sub get_child { my $self = shift; my $children = $self->{'curr_node'}->children; print "[get_child] children: $children\n" if $multi_tree::debug; my $pos = shift || $self->{'curr_pos'}; unless( $pos <= $#{$children} ) { my $num = $#{$children}; confess "Error, $pos is an invalid position [$num] $children.\n"; } print "[get_child] returning [$pos]: ", ${$children}[$pos], "\n" if $multi_tree::debug; return( ${$children}[$pos] ); } sub add_child { my $self = shift; my($key,$value,$pos) = @_; my $children = $self->{'curr_node'}->children; print "[add_child] children: $children\n" if $multi_tree::debug; my $curr_pos = $self->{'curr_pos'}; my $curr_node = $self->{'curr_node'}; print "[add_child] adding child $child ($key,$value) to: $children\n" if $multi_tree::debug; my $child = multi_tree::node->new($key,$value); $child->{'parent'} = $curr_node; if(defined $pos) { print "[add_child] adding at $pos $child\n" if $multi_tree::debug; unless($pos <= $#{$children}) { my $num = $#{$children}; confess "Position $pos is invalid for child position [$num] $children.\n"; } splice( @{$children}, $pos, 1, $child, ${$children}[$pos] ); } else { print "[add_child] adding at end $child\n" if $multi_tree::debug; push @{$children}, $child; } print "[add_child] children:", join(',',@{$self->{'curr_node'}->children}), "\n" if $multi_tree::debug; } sub position { my $self = shift; my $pos = shift; unless( defined $pos ) { return $self->{'curr_pos'}; } my $children = $self->{'curr_node'}->children; print "[position] children: $children\n" if $multi_tree::debug; unless( $pos <= $#{$children} ) { my $num = $#{$children}; confess "Error, $pos is invalid [$num] $children.\n"; } $self->{'pos'} = $pos; $self->{'curr_child'} = $self->get_child($pos); return $self->{'pos'}; } sub first { my $self = shift; $self->{'curr_pos'} = 0; $self->{'curr_child'} = $self->get_child(0); print "[first] set child[",$self->{'curr_pos'},"]: ",$self->{'curr_child'}, "\n" if $multi_tree::debug; return $self->{'curr_pos'}; } sub next { my $self = shift; my $pos = $self->{'curr_pos'} + 1; my $children = $self->{'curr_node'}->children; print "[next] children: $children\n" if $multi_tree::debug; unless( $pos >= 0 && $pos <= $#{$children} ) { return undef; } $self->{'curr_pos'} = $pos; $self->{'curr_child'} = $self->get_child($pos); return $self->{'curr_pos'}; } sub prev { my $self = shift; my $pos = $self->{'curr_pos'} - 1; my $children = $self->{'curr_node'}->children; print "[prev] children: $children\n" if $multi_tree::debug; unless( $pos >= 0 && $pos <= $#{$children} ) { return undef; } $self->{'curr_pos'} = $pos; $self->{'curr_child'} = $self->get_child($pos); return $self->{'curr_pos'}; } sub last { my $self = shift; my $children = $self->{'curr_node'}->children; my $pos = $#{$children}; print "[last] children [$pos]: $children\n" if $multi_tree::debug; $self->{'curr_pos'} = $pos; $self->{'curr_child'} = $self->get_child($pos); return $self->{'curr_pos'}; } sub down { my $self = shift; my $pos = shift; my $children = $self->{'curr_node'}->children; print "[down] children: $children\n" if $multi_tree::debug; if( defined $pos ) { unless( $self->position($pos) ) { confess "Error, $pos was an invalid position.\n"; } } $self->{'curr_pos'} = undef; $self->{'curr_node'} = $self->{'curr_child'}; $self->{'curr_child'} = undef; print "[down] set to: ", $self->{'curr_node'}, "\n" if $multi_tree::debug; return 1; } sub up { my $self = shift; my $node = $self->{'curr_node'}; my $parent = $node->parent(); unless( defined $parent ) { return undef; } $self->{'curr_pos'} = undef; $self->{'curr_node'} = $parent; $self->{'curr_child'} = undef; return 1; } sub top { my $self = shift; my $tree = $self->{'tree'}; $self->{'curr_pos'} = undef; $self->{'curr_node'} = $tree->{'top'}; $self->{'curr_child'} = undef; return 1; } sub children { my $self = shift; my $children = $self->{'curr_node'}->children; return @{$children}; } 1;