in reply to changing array size in foreach loop: is it safe?

First off, while not exactly perfectly worded for this question (is push different from splice?), the foreach documentation in perlsyn says, in part:

If any part of LIST is an array, "foreach" will get very confus +ed if you add or remove elements within the loop body, for example wi +th "splice". So don’t do that.
I think that says "no".

Now, as to how to do this: no, I don't know a clean way to do that, either. map doesn't quite do it:

@foo = map { 'd' eq $_ ? ($_,'h') : $_ } @foo; # puts 'h' right after 'd'
And using an extra variable doesn't quite help, either:
my @extras; foreach my $x (@foo) { push @extras, 'h' if $x eq 'd'; print $x; } push @foo, @extras; # didn't print 'h'. But if we add: print foreach @extras; # that works ... but duplicates code.
Looks like the guaranteed solution is a bit longer:
{ my @extras; foreach my $x (@foo) { push @extras, 'h' if $x eq 'd'; } push @foo, @extras; } foreach my $x (@foo) { print $x; }
Even that misses it in some cases - for example, if what you're pushing onto @foo may need to be treated itself. In this case, imagine pushing on a random letter which itself may be 'd' - and thus you want to push on an extra random letter each time you get another 'd'. Theoretically this may not ever end, but practically you'll stop getting 'd's eventually.

Replies are listed 'Best First'.
Re^2: changing array size in foreach loop: is it safe?
by RazorbladeBidet (Friar) on Feb 07, 2005 at 18:07 UTC
    If any part of LIST is an array, "foreach" will get very 
    confused if you add or remove elements within the loop 
    body, for example with "splice".   So don’t do that.
    
    Isn't this just saying if one is manipulating an element that is itself an array, not the LIST itself? I would think as long as one stuck to scalars, it should be OK, although one would have to be careful.

    Update: This one works... iffy (same ballpark), but works:
    @foo = qw/a b c d e f g/; map{ push @foo, 'h' if /^d$/ } @foo; print @foo, "\n";

    I think it's a case of simply having enough rope to hang yourself.
Re^2: changing array size in foreach loop: is it safe?
by Roy Johnson (Monsignor) on Feb 07, 2005 at 19:27 UTC
    Your last description calls for reprocessing @extras until it is empty:
    my @foo = qw/a b c d e f g/; my $sub = 'h'; for (my @extras = @foo; @extras; @extras = my @new_extras) { foreach my $x (@extras) { push @new_extras, $sub++ if $x =~ /[dhij]/; } push @foo, @new_extras; } print for @foo; # Tacks on h when it hits the d, then # i for the h, j for the i, and k for the j # yielding abcdefghijk
    I like how the for replaces the bare block for scoping.

    You can also do it without the @new_extras variable by replacing the foreach loop/push combination with a map:

    my @foo = qw/a b c d e f g/; my $sub = 'h'; for (my @extras = @foo; @extras;) { @extras = map { /[dhij]/ ? $sub++ : () } @extras; push @foo, @extras; } print for @foo;

    Caution: Contents may have been coded under pressure.