package Graph::Traversal; use strict; # $SIG{__DIE__ } = sub { use Carp; confess }; # $SIG{__WARN__} = sub { use Carp; confess }; sub DEBUG () { 0 } sub reset { my $self = shift; $self->{ unseen } = { map { $_ => $_ } $self->{ graph }->vertices }; $self->{ seen } = { }; $self->{ order } = [ ]; $self->{ preorder } = [ ]; $self->{ postorder } = [ ]; $self->{ roots } = [ ]; $self->{ tree } = Graph->new( directed => $self->{ graph }->directed ); delete $self->{ terminate }; } my $see = sub { my $self = shift; $self->see; }; my $see_active = sub { my $self = shift; delete @{ $self->{ active } }{ $self->see }; }; sub has_a_cycle { my ($u, $v, $t, $s) = @_; $s->{ has_a_cycle } = 1; $t->terminate; } sub find_a_cycle { my ($u, $v, $t, $s) = @_; my @cycle = ( $u ); push @cycle, $v unless $u eq $v; my $path = $t->{ order }; if (@$path) { my $i = $#$path; while ($i >= 0 && $path->[ $i ] ne $v) { $i-- } if ($i >= 0) { unshift @cycle, @{ $path }[ $i+1 .. $#$path ]; } } $s->{ a_cycle } = \@cycle; $t->terminate; } sub configure { my ($self, %attr) = @_; $self->{ pre } = $attr{ pre } if exists $attr{ pre }; $self->{ post } = $attr{ post } if exists $attr{ post }; $self->{ pre_vertex } = $attr{ pre_vertex } if exists $attr{ pre_vertex }; $self->{ post_vertex } = $attr{ post_vertex } if exists $attr{ post_vertex }; $self->{ pre_edge } = $attr{ pre_edge } if exists $attr{ pre_edge }; $self->{ post_edge } = $attr{ post_edge } if exists $attr{ post_edge }; if (exists $attr{ successor }) { # Graph 0.201 compatibility. $self->{ tree_edge } = $self->{ non_tree_edge } = $attr{ successor }; } if (exists $attr{ unseen_successor }) { if (exists $self->{ tree_edge }) { # Graph 0.201 compatibility. my $old_tree_edge = $self->{ tree_edge }; $self->{ tree_edge } = sub { $old_tree_edge->( @_ ); $attr{ unseen_successor }->( @_ ); }; } else { $self->{ tree_edge } = $attr{ unseen_successor }; } } if ($self->graph->multiedged || $self->graph->countedged) { $self->{ seen_edge } = $attr{ seen_edge } if exists $attr{ seen_edge }; if (exists $attr{ seen_successor }) { # Graph 0.201 compatibility. $self->{ seen_edge } = $attr{ seen_edge }; } } $self->{ non_tree_edge } = $attr{ non_tree_edge } if exists $attr{ non_tree_edge }; $self->{ pre_edge } = $attr{ tree_edge } if exists $attr{ tree_edge }; $self->{ back_edge } = $attr{ back_edge } if exists $attr{ back_edge }; $self->{ down_edge } = $attr{ down_edge } if exists $attr{ down_edge }; $self->{ cross_edge } = $attr{ cross_edge } if exists $attr{ cross_edge }; if (exists $attr{ start }) { $attr{ first_root } = $attr{ start }; $attr{ next_root } = undef; } if (exists $attr{ get_next_root }) { $attr{ next_root } = $attr{ get_next_root }; # Graph 0.201 compat. } $self->{ next_root } = exists $attr{ next_root } ? $attr{ next_root } : $attr{ next_alphabetic } ? \&Graph::_next_alphabetic : $attr{ next_numeric } ? \&Graph::_next_numeric : \&Graph::_next_random; $self->{ first_root } = exists $attr{ first_root } ? $attr{ first_root } : exists $attr{ next_root } ? $attr{ next_root } : $attr{ next_alphabetic } ? \&Graph::_next_alphabetic : $attr{ next_numeric } ? \&Graph::_next_numeric : \&Graph::_next_random; $self->{ next_successor } = exists $attr{ next_successor } ? $attr{ next_successor } : $attr{ next_alphabetic } ? \&Graph::_next_alphabetic : $attr{ next_numeric } ? \&Graph::_next_numeric : \&Graph::_next_random; if (exists $attr{ has_a_cycle }) { my $has_a_cycle = ref $attr{ has_a_cycle } eq 'CODE' ? $attr{ has_a_cycle } : \&has_a_cycle; $self->{ back_edge } = $has_a_cycle; if ($self->{ graph }->is_undirected) { $self->{ down_edge } = $has_a_cycle; } } if (exists $attr{ find_a_cycle }) { my $find_a_cycle = ref $attr{ find_a_cycle } eq 'CODE' ? $attr{ find_a_cycle } : \&find_a_cycle; $self->{ back_edge } = $find_a_cycle; if ($self->{ graph }->is_undirected) { $self->{ down_edge } = $find_a_cycle; } } $self->{ add } = \&add_order; $self->{ see } = $see; delete @attr{ qw( pre post pre_edge post_edge successor unseen_successor seen_successor tree_edge non_tree_edge back_edge down_edge cross_edge seen_edge start get_next_root next_root next_alphabetic next_numeric next_random first_root has_a_cycle find_a_cycle ) }; if (keys %attr) { require Carp; my @attr = sort keys %attr; Carp::croak(sprintf "Graph::Traversal: unknown attribute%s @{[map { qq['$_'] } @attr]}\n", @attr == 1 ? '' : 's'); } } sub new { my $class = shift; my $g = shift; unless (ref $g && $g->isa('Graph')) { require Carp; Carp::croak("Graph::Traversal: first argument is not a Graph"); } my $self = { graph => $g, state => { } }; bless $self, $class; $self->reset; $self->configure( @_ ); return $self; } sub terminate { my $self = shift; $self->{ terminate } = 1; } sub add_order { my ($self, @next) = @_; push @{ $self->{ order } }, @next; } sub visit { my ($self, @next) = @_; delete @{ $self->{ unseen } }{ @next }; print "unseen = @{[sort keys %{$self->{unseen}}]}\n" if DEBUG; @{ $self->{ seen } }{ @next } = @next; print "seen = @{[sort keys %{$self->{seen}}]}\n" if DEBUG; $self->{ add }->( $self, @next ); print "order = @{$self->{order}}\n" if DEBUG; if (exists $self->{ pre }) { my $p = $self->{ pre }; for my $v (@next) { $p->( $v, $self ); } } } sub visit_preorder { my ($self, @next) = @_; push @{ $self->{ preorder } }, @next; for my $v (@next) { $self->{ preordern }->{ $v } = $self->{ preorderi }++; } print "preorder = @{$self->{preorder}}\n" if DEBUG; $self->visit( @next ); } sub visit_postorder { my ($self) = @_; my @post = reverse $self->{ see }->( $self ); push @{ $self->{ postorder } }, @post; for my $v (@post) { $self->{ postordern }->{ $v } = $self->{ postorderi }++; } print "postorder = @{$self->{postorder}}\n" if DEBUG; if (exists $self->{ post }) { my $p = $self->{ post }; for my $v (@post) { $p->( $v, $self ) ; } } if (exists $self->{ post_edge }) { my $p = $self->{ post_edge }; my $u = $self->current; if (defined $u) { for my $v (@post) { $p->( $u, $v, $self, $self->{ state }); } } } } sub _callbacks { my ($self, $current, @all) = @_; return unless @all; my $nontree = $self->{ non_tree_edge }; my $back = $self->{ back_edge }; my $down = $self->{ down_edge }; my $cross = $self->{ cross_edge }; my $seen = $self->{ seen_edge }; my $bdc = defined $back || defined $down || defined $cross; if (defined $nontree || $bdc || defined $seen) { my $u = $current; my $preu = $self->{ preordern }->{ $u }; my $postu = $self->{ postordern }->{ $u }; for my $v ( @all ) { my $e = $self->{ tree }->has_edge( $u, $v ); if ( !$e && (defined $nontree || $bdc) ) { if ( exists $self->{ seen }->{ $v }) { $nontree->( $u, $v, $self, $self->{ state }) if $nontree; if ($bdc) { my $postv = $self->{ postordern }->{ $v }; if ($back && (!defined $postv || $postv >= $postu)) { $back ->( $u, $v, $self, $self->{ state }); } else { my $prev = $self->{ preordern }->{ $v }; if ($down && $prev > $preu) { $down ->( $u, $v, $self, $self->{ state }); } elsif ($cross && $prev < $preu) { $cross->( $u, $v, $self, $self->{ state }); } } } } } if ($seen) { my $c = $self->graph->get_edge_count($u, $v); while ($c-- > 1) { $seen->( $u, $v, $self, $self->{ state } ); } } } } } sub next { my $self = shift; return undef if $self->{ terminate }; my @next; while ($self->seeing) { my $current = $self->current; print "current = $current\n" if DEBUG; @next = $self->{ graph }->successors( $current ); print "next.0 - @next\n" if DEBUG; my %next; @next{ @next } = @next; # delete $next{ $current }; print "next.1 - @next\n" if DEBUG; @next = keys %next; my @all = @next; print "all = @all\n" if DEBUG; delete @next{ $self->seen }; @next = keys %next; print "next.2 - @next\n" if DEBUG; if (@next) { @next = $self->{ next_successor }->( $self, \%next ); print "next.3 - @next\n" if DEBUG; for my $v (@next) { $self->{ tree }->add_edge( $current, $v ); } if (exists $self->{ pre_edge }) { my $p = $self->{ pre_edge }; my $u = $self->current; for my $v (@next) { $p->( $u, $v, $self, $self->{ state }); } } last; } else { $self->visit_postorder; } return undef if $self->{ terminate }; $self->_callbacks($current, @all); # delete $next{ $current }; } print "next.4 - @next\n" if DEBUG; unless (@next) { unless ( @{ $self->{ roots } } ) { my $first = $self->{ first_root }; if (defined $first) { @next = ref $first eq 'CODE' ? $self->{ first_root }->( $self, $self->{ unseen } ) : $first; return unless @next; } } unless (@next) { return unless defined $self->{ next_root }; return unless @next = $self->{ next_root }->( $self, $self->{ unseen } ); } return if exists $self->{ seen }->{ $next[0] }; # Sanity check. print "next.5 - @next\n" if DEBUG; push @{ $self->{ roots } }, $next[0]; } print "next.6 - @next\n" if DEBUG; if (@next) { $self->visit_preorder( @next ); } return $next[0]; } sub _order { my ($self, $order) = @_; 1 while defined $self->next; my $wantarray = wantarray; if ($wantarray) { @{ $self->{ $order } }; } elsif (defined $wantarray) { shift @{ $self->{ $order } }; } } sub preorder { my $self = shift; $self->_order( 'preorder' ); } sub postorder { my $self = shift; $self->_order( 'postorder' ); } sub unseen { my $self = shift; keys %{ $self->{ unseen } }; } sub seen { my $self = shift; keys %{ $self->{ seen } }; } sub seeing { my $self = shift; @{ $self->{ order } }; } sub roots { my $self = shift; @{ $self->{ roots } }; } sub is_root { my ($self, $v) = @_; for my $u (@{ $self->{ roots } }) { return 1 if $u eq $v; } return 0; } sub tree { my $self = shift; $self->{ tree }; } sub graph { my $self = shift; $self->{ graph }; } sub vertex_by_postorder { my ($self, $i) = @_; exists $self->{ postorder } && $self->{ postorder }->[ $i ]; } sub postorder_by_vertex { my ($self, $v) = @_; exists $self->{ postordern } && $self->{ postordern }->{ $v }; } sub postorder_vertices { my ($self, $v) = @_; exists $self->{ postordern } ? %{ $self->{ postordern } } : (); } sub vertex_by_preorder { my ($self, $i) = @_; exists $self->{ preorder } && $self->{ preorder }->[ $i ]; } sub preorder_by_vertex { my ($self, $v) = @_; exists $self->{ preordern } && $self->{ preordern }->{ $v }; } sub preorder_vertices { my ($self, $v) = @_; exists $self->{ preordern } ? %{ $self->{ preordern } } : (); } sub has_state { my ($self, $var) = @_; exists $self->{ state } && exists $self->{ state }->{ $var }; } sub get_state { my ($self, $var) = @_; exists $self->{ state } ? $self->{ state }->{ $var } : undef; } sub set_state { my ($self, $var, $val) = @_; $self->{ state }->{ $var } = $val; return 1; } sub delete_state { my ($self, $var) = @_; delete $self->{ state }->{ $var }; delete $self->{ state } unless keys %{ $self->{ state } }; return 1; } 1; __END__ =pod =head1 NAME Graph::Traversal - traverse graphs =head1 SYNOPSIS Don't use Graph::Traversal directly, use Graph::Traversal::DFS or Graph::Traversal::BFS instead. use Graph; my $g = Graph->new; $g->add_edge(...); use Graph::Traversal::...; my $t = Graph::Traversal::...->new(%opt); $t->... =head1 DESCRIPTION You can control how the graph is traversed by the various callback parameters in the C<%opt>. In the parameters descriptions below the $u and $v are vertices, and the $self is the traversal object itself. =head2 Callback parameters The following callback parameters are available: =over 4 =item tree_edge Called when traversing an edge that belongs to the traversal tree. Called with arguments ($u, $v, $self). =item non_tree_edge Called when an edge is met which either leads back to the traversal tree (either a C, a C, or a C). Called with arguments ($u, $v, $self). =item pre_edge Called for edges in preorder. Called with arguments ($u, $v, $self). =item post_edge Called for edges in postorder. Called with arguments ($u, $v, $self). =item back_edge Called for back edges. Called with arguments ($u, $v, $self). =item down_edge Called for down edges. Called with arguments ($u, $v, $self). =item cross_edge Called for cross edges. Called with arguments ($u, $v, $self). =item pre =item pre_vertex Called for vertices in preorder. Called with arguments ($v, $self). =item post =item post_vertex Called for vertices in postorder. Called with arguments ($v, $self). =item first_root Called when choosing the first root (start) vertex for traversal. Called with arguments ($self, $unseen) where $unseen is a hash reference with the unseen vertices as keys. =item next_root Called when choosing the next root (after the first one) vertex for traversal (useful when the graph is not connected). Called with arguments ($self, $unseen) where $unseen is a hash reference with the unseen vertices as keys. If you want only the first reachable subgraph to be processed, set the next_root to C. =item start Identical to defining C and undefining C. =item next_alphabetic Set this to true if you want the vertices to be processed in alphabetic order (and leave first_root/next_root undefined). =item next_numeric Set this to true if you want the vertices to be processed in numeric order (and leave first_root/next_root undefined). =back The parameters C and C have a 'hierarchy' of how they are determined: if they have been explicitly defined, use that value. If not, use the value of C, if that has been defined. If not, use the value of C, if that has been defined. If not, the next vertex to be visited is chose randomly. =head2 Methods The following methods are available: =over 4 =item unseen Return the unseen vertices in random order. =item seen Return the seen vertices in random order. =item seeing Return the active fringe vertices in random order. =item preorder Return the vertices in preorder traversal order. =item postorder Return the vertices in postorder traversal order. =item vertex_by_preorder $v = $t->vertex_by_preorder($i) Return the ith (0..$V-1) vertex by preorder. =item preorder_by_vertex $i = $t->preorder_by_vertex($v) Return the preorder index (0..$V-1) by vertex. =item vertex_by_postorder $v = $t->vertex_by_postorder($i) Return the ith (0..$V-1) vertex by postorder. =item postorder_by_vertex $i = $t->postorder_by_vertex($v) Return the postorder index (0..$V-1) by vertex. =item preorder_vertices Return a hash with the vertices as the keys and their preorder indices as the values. =item postorder_vertices Return a hash with the vertices as the keys and their postorder indices as the values. =item tree Return the traversal tree as a graph. =item has_state $t->has_state('s') Test whether the traversal has state 's' attached to it. =item get_state $t->get_state('s') Get the state 's' attached to the traversal (C if none). =item set_state $t->set_state('s', $s) Set the state 's' attached to the traversal. =item delete_state $t->delete_state('s') Delete the state 's' from the traversal. =back =head2 Backward compatibility The following parameters are for backward compatibility to Graph 0.2xx: =over 4 =item get_next_root Like C. =item successor Identical to having C both C defined to be the same. =item unseen_successor Like C. =item seen_successor Like C. =back =head2 Special callbacks If in a callback you call the special C method, the traversal is terminated, no more vertices are traversed. =head1 SEE ALSO L, L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut