http://www.perlmonks.org?node_id=197886

Elgon has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

on a similar point to Limbic~Region's (sounds like a Culture shipname to me) regarding array indices, I have a question on this one: Let's say that I have an array from which I wish to remove a record (in the sense of splice - like a DNA base deletion.) I am using the following code:

for ($index = 0; $index <= (scalar(@array) - 1); ++$index) { if ($array[$index] == $whatever) { splice ("array, $index, 1); } }

Or it could be done this way, I suppose...

$index = 0; foreach $thing(@array) { if ($thing == $whatever) { splice (@array, $index, 1); } ++$index; }

Personally I find the for loop somewhat messy and would prefer a tidy foreach instead, however the second way seems equally crufty. Is there simply a far more elegant way of doing this of which I am unaware? Limbic~Region's hypothetical function does do what I want but I was wondering if there was some *magic variable* used by Perl as an iterator in foreach loops somewhat like that found in the each function for use with hashes.

Of the solutions so far, I prefer the first but am very willing to listen to other ideas.

Elgon

"Rule #17 of Travel: Never try and score dope off Hassidic Jews while under the impression that they are Rastafarians."
       - Pete McCarthy, McCarthy's Bar

Replies are listed 'Best First'.
Re: Yet More fun With Array Indices
by japhy (Canon) on Sep 14, 2002 at 16:48 UTC
    But you don't need to do this -- you can just use grep() and be done with it: @ok = grep !bad(), @orig;.

    Also, it's considered dangerous to resize an array while looping over it (Perl-style), and difficult to resive an array while looping over it (index-style).

    my $i = 0; while ($i < @array) { if (REMOVE) { splice @array, $i, 1 } else { ++$i } }
    See? Only increment the index if the element's not being removed. This is why grep() is king. grep() is the True Way.

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker, who'd like a job (NYC-area)
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Re: Yet More fun With Array Indices
by blokhead (Monsignor) on Sep 14, 2002 at 16:51 UTC
    If you're just removing items that match/don't match a pattern, grep is the easiest choice:
    my @array = qw/1 2 3 4 5 6 7 8 9 10 11 12/; my $pattern = qr/2/; @array = grep { ! /$pattern/ } @array; # @array now contains qw/1 3 4 5 6 7 8 9 10 11/

    blokhead

      Bullseye - I don't understand why everyone else got stuck on the for loop. Some minor points though: if you're using patterns for matches, you should consider using anchors: @array = grep { ! /^$pattern$/ } @array; In this case it's better to just use a comparison: @array = grep $_ ne $whatever, @array;

      Makeshifts last the longest.

      Since Elgon's original code appeared to be doing numeric tests, it's worth pointing out that grep will work with numeric operations as well as string matches. For instance:
      @a=(1,2,3,4,5); @b=grep {int($_/2) == 1} @a; print join $/,@b,"";
      prints out the values 2 and 3.

      Once again my faith in Perlmonks is vindicated - Deliciously lazy, redolent with hubris and positively teeming with impatience. Cracking solution.

      Thanks, Elgon.

      "Rule #17 of Travel: Never try and score dope off Hassidic Jews while under the impression that they are Rastafarians."
             - Pete McCarthy, McCarthy's Bar

Re: Yet More fun With Array Indices
by elusion (Curate) on Sep 14, 2002 at 16:47 UTC
    Something like this perhaps?
    foreach my $index (0..$#array) { if ($array[$index] == $whatever) { splice @array, $index, 1; } }
    Or, if you use $_ and a for loop:
    for (0..$#array) { if ($array[$_] == $whatever) { splice @array, $_, 1; } }
    One last choice to build: using an if statement modifier
    for (0..$#array) { splice @array, $_, 1 if $array[$_] == $whatever; }
    Except all of these are really flawed, because it skips elements. Removing an element makes all the future indices one shorter than they really are. An array of (1, 2, 1, 1, 2) will be changed to (2, 1, 2) if $whatever equals 1, because it skips around. To fix this, iterate backwards through the array, like so:
    for (reverse 0..$#array) { splice @array, $_, 1 if $array[$_] == $whatever; }

    elusion : http://matt.diephouse.com