Resource-Silo

 view release on metacpan or  search on metacpan

lib/Resource/Silo/Metadata/DAG.pm  view on Meta::CPAN


List only vertices with no outgoing edges.

=cut

sub list_sinks {
    my $self = shift;

    return grep { !$self->edges_out->{$_} } keys %{$self->edges_in};
}

=head2 list_predecessors(\@list)

Given a list of vertices, return the list of all their predecessors
without the vertices themselves.

=cut

sub list_predecessors {
    my ($self, $list) = @_;
    my %uniq;
    foreach my $node (@$list) {
        next unless $self->edges_in->{$node};
        @uniq{ keys %{$self->edges_in->{$node}} } = ();
    };
    delete $uniq{$_} for @$list; # remove self-references
    return keys %uniq;
}

=head2 contains($name)

Returns true if a vertex named C<$name> is present.

=cut

sub contains {
    my ($self, $name) = @_;
    return exists $self->edges_out->{$name}
        || exists $self->edges_in->{$name};
}

=head2 add_edges (\@from, \@to)

Add edges from first vertex to the following ones.

=cut

sub add_edges {
    my ($self, $from, $to) = @_;

    foreach my $consumer (@$from) {
        foreach my $producer (@$to) {
            next if $consumer eq $producer; # self-dependency is ignored
            $self->edges_out->{$consumer}->{$producer} = 1;
            $self->edges_in->{$producer}->{$consumer} = 1;
        }
    }
    return;
}

=head2 drop_sink_cascade($name)

If $name is a sink, remove it along with any vertex which becomes
a sink as a result of the operation, propagating along the edges.

Otherwise do nothing.

=cut

sub drop_sink_cascade {
    my ($self, $arriving) = @_;

    my @queue = ($arriving);
    while (@queue) {
        my $producer = shift @queue;
        next if $self->edges_out->{$producer}; # producer is not independent => skip
        my $node = delete $self->edges_in->{$producer};
        next unless $node; # no one is waiting => skip

        foreach my $consumer (keys %$node) {
            my $still_waiting = $self->edges_out->{$consumer};
            delete $still_waiting->{$producer};
            if (keys %$still_waiting == 0) {
                delete $self->edges_out->{$consumer};
                push @queue, $consumer;
            }
        }
    }
}

=head2 find_loop ($start, \@list, \%seen)

Find out whether calling C<< $self->add_dependency([$start], $list) >>
would cause a loop in the graph.

Due to the usage scenario, it's disjoint from adding vertices/edges.

=cut

sub find_loop {
    # before inserting a new edge, check if it would create a loop
    my ($self, $start, $list, $seen) = @_;

    foreach my $next (@$list) {
        return [$start] if $next eq $start; # loop found
        next if $seen->{$next}++;
        my $out = $self->edges_out->{$next} or next;
        my $loop = $self->find_loop($start, [ keys %$out ], $seen);
        return [$next, @$loop] if $loop;
    }

    return;
}

=head2 self_check

Check the internal structure of the graph, returning C<undef> if its intact,
or an arrayref containing the list of discrepancies otherwise.

=cut

sub self_check {
    my $self = shift;

    my @mismatch; # "consumer -> producer" or "producer <- consumer"

    foreach my $consumer (keys %{$self->edges_out}) {
        foreach my $producer (keys %{$self->edges_out->{$consumer}}) {
            push @mismatch, "$consumer <- $producer"
                unless $self->edges_in->{$producer}



( run in 0.684 second using v1.01-cache-2.11-cpan-13bb782fe5a )