perlquestion
Eily
<p>Again, some of you might have seen the question on the CB already. (And again, I came up with a solution just because asking made me see things clear enough). I am wondering about iterating over an hash while removing some of its keys (the current one, and several connected ones). Actually, to explain my problem on the CB I said I'd like to be able to do this:
<c>while (my ($k,$v) = first %hash)
{
delete $hash{$_} for $k, @$v;
}</c> where first does the same as [doc://each], but with a new iterator (ie, from the start). Long story short, this led to this:
<c>use strict;
use warnings;
use feature 'say';
use Data::Dump qw( pp );
sub first (\%)
{
my $hash = shift;
scalar(keys %$hash); # Force reset
each %$hash;
}
my %hash =
(
1 => [2..7],
8 => [ 9 ],
9 => [ 8 ],
(map { $_ => [ 1 ] } 2..7),
);
say "Input hash:", pp \%hash;
say "";
say "Try first then each twice in a row to check that the iteration is restarted";
say pp [first %hash], [each %hash] for 1..2;
say "";
# Delete a key and all the keys contained in the value array
sub rec_delete(\%$);
sub rec_delete(\%$)
{
my ($hash, $key) = @_;
my $array = delete $hash->{$key};
return unless $array;
say " Removing $key -> [ @$array ]";
rec_delete %$hash => $_ for @$array;
}
while (my ($k,$v) = first %hash)
{
say "Starting from $k -> [ @$v ]";
rec_delete %hash => $k;
}</c>
<c>Input hash:{ 1 => [2 .. 7], 2 => [1], 3 => [1], 4 => [1], 5 => [1], 6 => [1], 7 => [1], 8 => [9], 9 => [8] }
Try first then each twice in a row to check that the iteration is restarted
([8, [9]], [5, [1]])
([8, [9]], [5, [1]])
Starting from 8 -> [ 9 ]
Removing 8 -> [ 9 ]
Removing 9 -> [ 8 ]
Starting from 5 -> [ 1 ]
Removing 5 -> [ 1 ]
Removing 1 -> [ 2 3 4 5 6 7 ]
Removing 2 -> [ 1 ]
Removing 3 -> [ 1 ]
Removing 4 -> [ 1 ]
Removing 6 -> [ 1 ]
Removing 7 -> [ 1 ]</c>
This works just fine. But I'm curious about other ways to do the same (because this seems like it could be a normal enough use of hashes?). So the exercise is:
<c>While the hash isn't empty
Take one key (at random) from the hash
Delete that key and all connected keys
</c> Without expanding the whole list of keys (let's pretend there's an awful lot of them, or the hash is tied, and it's harder to find an existing key than fetch a value). So <c>@keys = keys %hash; for my $key (@keys) { recursive_delete(\%hash, $key); } </c> isn't valid.</p>
<p>NB: the output of my example is actually random, so you might get a different cycle by trying it.</p>