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 | [reply] [Watch: Dir/Any] [d/l] |
|
Ahhh, I never thought about using a subroutine in the grep expresssion! That's really good info.
| [reply] [Watch: Dir/Any] |
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
| [reply] [Watch: Dir/Any] [d/l] [select] |
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. | [reply] [Watch: Dir/Any] [d/l] |
|
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
| [reply] [Watch: Dir/Any] [d/l] |
|
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.
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
|
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
| [reply] [Watch: Dir/Any] [d/l] |
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.
| [reply] [Watch: Dir/Any] [d/l] |
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 | [reply] [Watch: Dir/Any] [d/l] [select] |
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}
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
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. | [reply] [Watch: Dir/Any] [d/l] [select] |