John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

OK, I know I can use grep to find things, and map or foreach to process each element. But how do I (easily and clearly!) locate an element and then remove it?

For hashes, the each iterator expressly allows you to delete that element without messing things up, and you have the key so it's possible.

For an array, the "key" is an integer, and that's not provided in the foreach.

grep returns a list and means something else in scalar context, but I can live with that, just add extra parens on the left side. Easy to mess up as an idiom, though.

I can maintain my own counter in the block of a foreach, use a C-style index loop, or move all the items from one list to another (I prefer to mod in-place since it's a reference parameter).

I certainly can do it. But I wonder if there is a better way to do it that I can add to my bag of tricks.

Thanks,
—John

  • Comment on What's the idiom for finding and removing the found @ARRAY element?

Replies are listed 'Best First'.
Re: What's the idiom for finding and removing the found @ARRAY element?
by jdporter (Paladin) on Nov 06, 2002 at 03:46 UTC
    If you absolutely must modify an array in place with deletes, then you could do it this way:
    for my $i ( reverse 0 .. $#a ) { splice( @a, $i, 1 ) if should_delete; }
    The point being that each time you delete an element, the indices of all elements after it in the array change.
    As an exercise, consider what the above code would do if the reverse were removed.

      For some cases, it will be faster to not move all of the elements down each time:

      my $o= 0; for my $i ( 0..$#$av ) { if( should_keep($i) ) { $av->[$o++]= $av->[$i]; } } $#$av= $o-1;

              - tye
        As far as that goes, it might be more efficient, in some cases, to not change the length of the array at all, and just set the "undesirable" elements to undef. Then, later, when the array is processed, just skip over the undef ones.
      Good point about the reverse, if I was removing more than one. That's what I'm doing, but returning out of the loop as soon as I found it.

      Now, can you apply the $# syntax to a reference? My brain gagged at that, even if Perl wouldn't (I went with scalar -1).

        my $arr = [(1) x 5]; print $#$arr;

        Makeshifts last the longest.

        Sure, that's what curly braces are for. (Well, one of the many things.)
        for my $i ( reverse 0 .. $#{ $ar } ) { splice @{ $ar }, 0, $i; }
      If you absolutely must modify an array in place with deletes, then you could do it this way:

      for my $i ( reverse 0 .. $#a ) { splice( @a, $i, 1 ) if should_delete; }

      You could but I wouldn't recommend it. Count down in a while loop.

      Normally when I feel the need to alter an array in place, I won't want to create the lists  0 through  $#a and  $#a through  0.

      The new foreach optimization of the range operator would be much more useful if the behaviour of $#a .. 0 had not changed. Oh well.

Re: What's the idiom for finding and removing the found @ARRAY element?
by bbfu (Curate) on Nov 06, 2002 at 03:52 UTC

    Hrm. Is there anything wrong with: @array = grep { not /$pattern/ } @array;

    Update: I guess I didn't register the parts of your post where you sort of talk about that. :) But I still think it's the clearest, unless you're wanting to remove just one element, even if it isn't the most efficient in many situtations. I tend to only bother remembering the clear idioms, as you can always look up the efficient ones if you really need to.

    bbfu
    Black flowers blossum
    Fearless on my breath

      I think he only needs it to work on a reference:

      $arrayref = [ grep { not /$pattern/ } @$arrayref ];
Re: What's the idiom for finding and removing the found @ARRAY element?
by Aristotle (Chancellor) on Nov 06, 2002 at 12:35 UTC
    splice @$ref, do { my $i = 0; $i++ until $i > $#$ref or $ref->[$i] eq 'foo'; $i }, 1;

    Makeshifts last the longest.

Re: What's the idiom for finding and removing the found @ARRAY element?
by Cody Pendant (Prior) on Nov 06, 2002 at 05:26 UTC
    I thought that in recent versions of perl you could do delete() on array elements, just like you can on hash keys?
    --
    ($_='jjjuuusssttt annootthheer pppeeerrrlll haaaccckkeer')=~y/a-z//s;print;

      You can. Unfortunately, all it seems to do is set the element to undef unless it also happens to be the last (highest) element in the array, in which case it does also remove it and reduces the overall size of the array.

      If it is any element other than the last, the array stays the same size, and the rest of the elements retain their previous indices.


      Nah! You're thinking of Simon Templar, originally played (on UKTV) by Roger Moore and later by Ian Ogilvy
      You can, delete $array[$index] as long as you note that all delete does is set that array position to an uninitialized state, but leaves the other elements in the array in their current positions (same amount of elements in the array but now one is unintialized). Unless, it is the last element that you deleted.