sub mk_kv; sub reverse_hoa; sub mk_kv { return $_[0] => [ @_[1 .. $#_] ]; } sub reverse_hoa { my %hoa = @_; my %reversed; for my $key ( keys %hoa ) { for my $value ( @{$hoa{$key}} ) { push @{$reversed{$value}}, $key } } return %reversed; } #### sub remove; sub remove_down; sub remove_up; my $down = { map { mk_kv split / /, $_ } split /\n/, $graph }; # $down->{$key} is an arrayref of the modules downstream from $key my $up = { reverse_hoa %$down }; # $up->{$key} is an arrayref of the modules upstream from $key remove $down, $up, $remove; # This returns an array listing the modules to be uninstalled, with the earliest leftmost. { my ( $down, $up ); my ( %removed, %to_remove, @spare ); sub remove { my $remove; ( $down, $up, $remove ) = @_; undef %removed; undef %to_remove; return remove_down($remove), remove_up; } sub remove_down { my ( $remove ) = @_; if ( exists $removed{$remove} ) { return () } else { undef $removed{$remove}; # $remove has been removed undef @to_remove{@spare} if @spare = @{$up->{$remove}}; # All modules up stream from $remove should be removed in post-processing # undef @hash{()} fails delete @to_remove{keys %removed}; # Don't remove in post-processing if already removed return map( { remove_down $_ } @{$down->{$remove}} ), $remove; } } sub remove_up { return ( @spare = keys %to_remove ) ? ( ( map { remove_down $_ } @spare ), remove_up ) # FALSE --v # The call to remove_down is almost certainly unnecessary, except for the side effect of setting keys in %to_remove # FALSE --^ : (); } }