Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Deleting specific element in array in FOREACH loop

by awohld (Hermit)
on Sep 15, 2006 at 19:24 UTC ( [id://573231]=perlquestion: print w/replies, xml ) Need Help??

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

Here is a very oversimplification of my problem. I have a foreach loop that loops over each element in a list. If after a long calculation I can delete it, I want to remove it from the array.

After looping over each element, I want to start over again on the elements that weren't deleted and try them again.

How would I get this code to work that way? Can I get the array index of the list element I'm currently evaluating inside the foreach loop?
#!/usr/bin/perl -w use strict; use Data::Dumper; my @array = qw( 1 2 3 4 5 6 7 ); foreach my $element ( @array ) { # Doing tons of stuff here to test element # Multiple DB Calls. For sake of testing, we'll only # test for even numbers. # Can I print the element number of the array here? # Delete element here if it matches. delete element being evaluated if it's even. } # Should print an array with all odd values. print Dumper \@array;

Replies are listed 'Best First'.
Re: Deleting specific element in array in FOREACH loop
by hgolden (Pilgrim) on Sep 15, 2006 at 19:50 UTC
    Grep is god's gift to this situation. Check out grep. Basically, you can write a subroutine that takes the array element as an input and returns true or false, true if it should be included.

    Then you write a version of  @newarray=grep {&subroutine($_)} @oldarray. The new array will only contain the elements that the subroutine returned true for.

    Hays

      Ahhh, I never thought about using a subroutine in the grep expresssion! That's really good info.
Re: Deleting specific element in array in FOREACH loop
by GrandFather (Saint) on Sep 15, 2006 at 19:57 UTC

    If you want to delete from the array you need to watch out for the trap that arrises when you delete an element and shift the remaining elements down one place. One way around it is to build a list of elements for deletion:

    #!/usr/bin/perl -w use strict; use Data::Dumper; my @array = qw( 1 2 3 4 5 6 7 ); my @delList; foreach my $index (0 .. $#array) { # Delete element here if it matches. push @delList, $index if ($array[$index] & 1) == 0; # Add for deleti +on } splice @array, $_, 1 for reverse @delList; print Dumper \@array;

    The down side is that you don't have an alias to the element, you have its index. Another way to do it is to push the elements you want to keep to another array:

    ... my @keep; foreach my $element (@array) { # Add element here if it doesn't match. push @keep, $element if ($element & 1) != 0; # Add for deletion } ...

    DWIM is Perl's answer to Gödel
Re: Deleting specific element in array in FOREACH loop
by perrin (Chancellor) on Sep 15, 2006 at 19:51 UTC
    There are lots of ways to do it. You could build up a new array as you go with only the values you want to keep. You could use an index and splice. For example (untested):
    for (my $i = $#array; $i > -1; $i--) { # Delete element here if it matches. splice @array, $i, 1; }
    You need to count backwards here so that you don't miss any entries when they shift around.
      for my $index (reverse 0 .. $#array)

      is a more Perlish way to achieve that. It is clearer and avoids fence posts.


      DWIM is Perl's answer to Gödel

        The downside is that
        for my $index (reverse 0 .. $#array)
        creates a list as large as the array, whereas
        for (my $i = $#array; $i > -1; $i--)
        and
        for (my $i = @array; $i--; )
        have no memory cost. That said, it usually doesn't matter.

        On the plus side,
        for my $index (reverse 0 .. $#array)
        could be slower. It is optimized to loop backwards instead of actually calling reverse.

Re: Deleting specific element in array in FOREACH loop
by ptum (Priest) on Sep 15, 2006 at 19:40 UTC

    For something like this, I usually use a hash rather than an array, so that I can use delete() on the hash element. Maybe something like this (untested):

    my %hash; for my $i (0 .. 7) { $hash{$i}++; } foreach (keys %hash) { # do stuff delete($hash{$i}) if ($condition); }

    No good deed goes unpunished. -- (attributed to) Oscar Wilde
Re: Deleting specific element in array in FOREACH loop
by swampyankee (Parson) on Sep 15, 2006 at 21:29 UTC

    If I interpret foreach correctly (the part where it says "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."), deleting elements of an array used in a foreach statement as done in the example

    foreach my $element ( @array ) {
    is fraught with peril.

    emc

    At that time [1909] the chief engineer was almost always the chief test pilot as well. That had the fortunate result of eliminating poor engineering early in aviation.

    —Igor Sikorsky, reported in AOPA Pilot magazine February 2003.
Re: Deleting specific element in array in FOREACH loop
by johngg (Canon) on Sep 15, 2006 at 20:24 UTC
    How about an array slice. Do your processing in a loop and store the index of each element you want to keep. Then slice the array at the end.

    use strict; use warnings; my @array; push @array, (int rand 50) + 1 for 1 .. 20; print qq{@array\n}; my @keep = (); for my $index (0 .. $#array) { push @keep, $index if $array[$index] % 2; } @array = @array[@keep]; print qq{@array\n};

    Here's sample output

    45 19 34 31 5 36 1 38 48 2 1 9 6 9 28 1 23 19 23 41 45 19 31 5 1 1 9 9 1 23 19 23 41

    Cheers,

    JohnGG

Re: Deleting specific element in array in FOREACH loop
by shmem (Chancellor) on Sep 15, 2006 at 20:15 UTC
    foreach (and for) build a list containing aliases to the elements passed in. You can delete elements from the underlying array as long as you are iterating linearly over the array, it's to say not relying on the index. Oh well, that sounds like mumble (examples & pitfalls not yet promised :-). Anyways, it seems that doing
    while(@array) { my $element = $array[0]; # do tons of stuff... if($result eq 'foo') { shift @array; } }

    is a better approach, since you want to consume the array with your loop.

    <update> - reread post - this doesn't fit.</update>

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Um, with your approach, if you don't actually shift every element off of @array, it'll be an infinite loop, I think. Maybe you meant something like:
      my @keep = (); while (@array) { my $element = shift @array; # do stuff push @keep, $element unless ( $result eq 'foo' ); }
      In any case, just doing  my @keep = grep { somefunc($_) } @array; (as suggested in earlier replies) feels easier and cleaner somehow.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://573231]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (4)
As of 2024-03-29 11:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found