Pushing scalars into temporary arrays to extract data is not as uncommon as it may seem;the side effect of such practices is, that memory is being wasted. Iterating through an array (lets say for (my $i; $i < @array; $i++)) and choping out unneeded items with splice() is a subtle trap since $#array will constantly be shrinked; how can we determine reliably at which array index to throw out an item without endangering its surrounding items?

Following piece of code attempts to outline such an approach. The sub &filter_stuff takes as first argument an arrayref, and as second argument an array consisting of regular expressions. If a certain item within the arrayref ($stuff) doesn't match all expressions, it will be choped out.

sub filter_stuff { my $stuff = shift; my @patterns = @_; for (my $match, my $i = 0; $i < @$stuff; $i++ if $match) { for (@patterns) { $match = $$stuff[$i] =~ /$_/; splice(@$stuff, $i, 1) && last unless $match; } } }

Replies are listed 'Best First'.
Re: extracting splices with a for-loop and splice()
by Roy Johnson (Monsignor) on Jan 28, 2004 at 18:12 UTC
    I would generally be inclined to use
    @ar = grep { my $all_matched=1; for my $p (@patterns) { last unless $all_matched = /$p/; } $all_matched; } @ar;
    but if reconstructing the array this way was a memory concern, I'd walk backward through the array so that anything I removed would not affect the indexes of elements I have yet to examine:
    sub filter_stuff { my $stuff = shift; my @patterns = @_; # Backwards iteration trick: # use negative of negative loop variable as index for my $i (-$#$stuff..0) { for (@patterns) { splice(@$stuff, -$i, 1) && last unless $stuff->[-$i] =~ /$_ +/; } } }
    Update: The non-tricky way of walking backward through an array, of course, is
    for (my $i=$#$stuff; $i >= 0; --$i) { #use $i as index }
    I just wanted to avoid the C-style loop, for religious reasons.

    The PerlMonk tr/// Advocate
      Latter one is indeed an outstanding idea.
Re: extracting splices with a for-loop and splice()
by rcaputo (Chaplain) on Jan 29, 2004 at 04:59 UTC

    Update: Roy Johnson already said as much over here.

    You won't need the special $match logic if you scan @$stuff backwards. An untested revision of your original function:

    sub filter_stuff { my ($stuff, @patterns) = @_; my $i = @$stuff; while ($i--) { for (@patterns) { next unless $stuff->[$i] =~ /$_/; splice(@$stuff, $i, 1); last; } } }

    When splice() removes an item, all the others with higher indexes move down. The loop has already checked $i+1 when it moves into position $i, so you can move on without checking it again.

    -- Rocco Caputo - rcaputo@pobox.com - poe.perl.org

Re: extracting splices with a for-loop and splice()
by ysth (Canon) on Jan 28, 2004 at 17:01 UTC
    I much prefer $stuff->[$i] to $$stuff[$i].
      I guess a matter of taste. I prefer the arrow operator when I'm deatling with objects references, but not with vars though.