Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

using shift on current array index within a foreach 2

Status
Not open for further replies.

1DMF

Programmer
Jan 18, 2005
8,795
GB
Hi,

Is it possible to loop an array and remove the current item with shift?

e.g.

Code:
my @arr = (1,2,3,4,5,6,7,8,9);
foreach my $item (@arr)
{
  shift if $item == 5;
}
print "@arr";
Which should result in a list of numbers (1,2,3,4,6,7,8,9)

Is it possible and is it advisable altering an array while looping it?

Thanks,1DMF

"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"

Free Dance Music Downloads
 
Hi

1DMF said:
Is it possible to loop an array and remove the current item with shift?
Certainly not with [tt]shift[/tt], as it always removes the first element of the array.

As you called [tt]shift[/tt] without parameter, it not removes from @arr, but from [tt]@ARGV[/tt] :
Perl:
[navy]@ARGV[/navy] [teal]=[/teal] [b]qw[/b][teal]{[/teal] a1 a2 a3 [teal]}[/teal][teal];[/teal]

[b]my[/b] [navy]@arr[/navy] [teal]=[/teal] [teal]([/teal][purple]1[/purple][teal],[/teal][purple]2[/purple][teal],[/teal][purple]3[/purple][teal],[/teal][purple]4[/purple][teal],[/teal][purple]5[/purple][teal],[/teal][purple]6[/purple][teal],[/teal][purple]7[/purple][teal],[/teal][purple]8[/purple][teal],[/teal][purple]9[/purple][teal]);[/teal]
[b]foreach[/b] [b]my[/b] [navy]$item[/navy] [teal]([/teal][navy]@arr[/navy][teal])[/teal]
[teal]{[/teal]
  [b]shift[/b] [b]if[/b] [navy]$item[/navy] [teal]==[/teal] [purple]5[/purple][teal];[/teal]
[teal]}[/teal]
[b]print[/b] [green][i]"\@arr : @arr\n"[/i][/green][teal];[/teal]

[b]print[/b] [green][i]"\@ARGV : @ARGV\n"[/i][/green][teal];[/teal]
Code:
@arr : 1 2 3 4 5 6 7 8 9
@ARGV : a2 a3

To remove any element by its index, you can use [tt]splice[/tt]. ( You can also use [tt]delete[/tt], but that one will just unset the element, leaving a gap in the array. )

But seems you want to remove element by its value, so better use [tt]grep[/tt] :
Perl:
[navy]@arr[/navy] [teal]=[/teal] [b]grep[/b] [teal]{[/teal] [navy]$_[/navy] [teal]!=[/teal] [purple]5[/purple] [teal]}[/teal] [navy]@arr[/navy][teal];[/teal]

[gray]# or[/gray]

[navy]@arr[/navy] [teal]=[/teal] [b]grep[/b] [teal]![/teal][green][i]/^5$/[/i][/green][teal],[/teal] [navy]@arr[/navy][teal];[/teal]

1DMF said:
Is it possible and is it advisable altering an array while looping it?
Not directly, as in Perl [tt]foreach[/tt] can not assign both the current index and value to loop control variables. So you will end with something like this :
Perl:
[b]my[/b] [navy]@arr[/navy] [teal]=[/teal] [teal]([/teal][purple]1[/purple][teal],[/teal][purple]2[/purple][teal],[/teal][purple]3[/purple][teal],[/teal][purple]4[/purple][teal],[/teal][purple]5[/purple][teal],[/teal][purple]6[/purple][teal],[/teal][purple]7[/purple][teal],[/teal][purple]8[/purple][teal],[/teal][purple]9[/purple][teal]);[/teal]
[b]foreach[/b] [b]my[/b] [navy]$item[/navy] [teal]([/teal][b]keys[/b] [navy]@arr[/navy][teal])[/teal]
[teal]{[/teal]
  [b]splice[/b] [navy]@arr[/navy][teal],[/teal] [navy]$item[/navy][teal],[/teal] [purple]1[/purple] [b]if[/b] [navy]$arr[/navy][teal][[/teal][navy]$item[/navy][teal]][/teal] [teal]==[/teal] [purple]5[/purple][teal];[/teal]
[teal]}[/teal]
[b]print[/b] [green][i]"\@arr : @arr\n"[/i][/green][teal];[/teal]
While that seems to work, if you turn on warnings, you will get this, which smells like not advisable :
use warnings; said:
[tt]Use of uninitialized value within @arr in numeric eq (==) at /home/master/1DMF.pl line 6.[/tt]


Feherke.
feherke.github.io
 
Feherke had lots of good suggestions, I would also add the possibility of using splice() in a C-Style for loop.

Perl:
my @arr = qw/a b c d e f g/;
print "BEFORE: " . join(",", @arr) . "\n";

for (my $i = 0; $i <= $#arr; $i++) {
	if ($arr[$i] eq 'd') {
		# Remove this element with splice()
		print 'removed element: ' . splice(@arr, $i, 1) , "\n";
		redo;	# Don't increment $i
	} else {
		print "$arr[$i]\n";
	}
}

print "AFTER: " . join(",", @arr) . "\n";
 
How come you are using the 'keys' reserved word on an array?

Does that return index numbers rather than actual content value?

In the end I opted for the push method...
Code:
my @arr1 = (1,2,3,4,5,6,7,8,9);
my @arr2;

foreach my $item (@arr1)
{
  push (@arr2,$item) unless $item == 5;
}

OK you end up with more copies of the data in more arrays than you would like, but I wasn't sure if that mattered so much.

I know on my OU courses they didn't allow you to remove or add items (only update) to a collection while looping it.

Dunno, do you think it is worth implementing the C-Style splice method? Is this going to still cause a warning? or is that resolved with the 'redo' keyword? I assume that starts the for loop from the top again?

"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"

Free Dance Music Downloads
 
Hi

1DMF said:
How come you are using the 'keys' reserved word on an array?
Sorry, forgot the warning about the version requirement :
man perlfunc said:
Called in list context, returns a list consisting of all the keys of the named hash, or in Perl 5.12 or later only, the indices of an array. Perl releases prior to 5.12 will produce a syntax error if you try to use an array argument.

1DMF said:
I know on my OU courses they didn't allow you to remove or add items (only update) to a collection while looping it.
That is generally true for enumerator-based looping.

1DMF said:
Dunno, do you think it is worth implementing the C-Style splice method? Is this going to still cause a warning? or is that resolved with the 'redo' keyword? I assume that starts the for loop from the top again?
That is simple, portable, efficient.

I skipped that one in my answer as it raises no interest in relation with your "is it advisable" question : it has nothing counter-advisable. As long as [tt]for[/tt]'s 2[sup]nd[/sup] expression ( [tt][navy]$i[/navy] [teal]<=[/teal] [navy]$#arr[/navy][/tt] ) is re-evaluated in each pass and always compare against the current array size, is safe to alter the array size in any way.

By the way, [tt]redo[/tt] just executes [tt]for[/tt]'s instruction block again, without executing [tt]for[/tt]'s 3[sup]rd[/sup] expression ( [tt][navy]$i[/navy][teal]++[/teal][/tt] ). That is necessary as deleting one element moves the next one at the index where the deleted one was earlier. So if the loop control variable would be incremented, the element following the deleted one would be skipped.

One simple trick to avoid caring about this problem is to loop in reverse order :
Perl:
[b]for[/b] [teal]([/teal][b]my[/b] [navy]$i[/navy] [teal]=[/teal] [navy]$#arr[/navy][teal];[/teal] [navy]$i[/navy] [teal]>=[/teal] [purple]0[/purple][teal];[/teal] [navy]$i[/navy][teal]--)[/teal] [teal]{[/teal]
  [b]splice[/b] [navy]@arr[/navy][teal],[/teal] [navy]$i[/navy][teal],[/teal] [purple]1[/purple] [b]if[/b] [navy]$arr[/navy][teal][[/teal][navy]$i[/navy][teal]][/teal] [b]eq[/b] [green][i]'d'[/i][/green][teal];[/teal]
[teal]}[/teal]


Feherke.
feherke.github.io
 
Thanks Feherke,

regarding 'keys' with @ , foreach against collections don't return objects necessarily in the order they reside in the collection.

so 'keys' against a hash, you cannot guarantee the order they will be returned.

does using 'keys' against an array suffer from the same problem or does it always return the indexes in numerical order?

is it more to do with the collection type rather than the looping method?

"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"

Free Dance Music Downloads
 
Hi

1DMF said:
does using 'keys' against an array suffer from the same problem or does it always return the indexes in numerical order?

is it more to do with the collection type rather than the looping method?
Never saw this explicitly affirmed, but personally I am sure [tt]keys[/tt] will always return array indexes in order. There is no reason to not do it.

The unpredictable order in case of hashes ( and generally, associative arrays in many other languages, ) is caused by the technique used internally to store the data in the memory.

Feherke.
feherke.github.io
 
well I reworked it using your reverse splice method and it seems to be working fine.

the real code was a little bit more complex than the example we were discussing, but the principle was the same.

Code:
    # loop Advisers to build data
    for(my $i = $#adv; $i >=0; $i--)
    {         
                
        # if not current member remove
        if(!exists $advisers{$adv[$i]->{'Adv_MemNo'}})
        {
            splice(@adv,$i,1);                
        }
        else
        {
            # adviser set totals
            $adv[$i]->{'Total'} = $adv[$i]->{'MORT'} + $adv[$i]->{'LOAN'} + $adv[$i]->{'INS'};
        
            # increment grand totals
            $morttot = $morttot + $adv[$i]->{'MORT'};
            $loantot = $loantot + $adv[$i]->{'LOAN'};
            $instot = $instot + $adv[$i]->{'INS'};
        }
        
    }
    my $globtot = $morttot + $loantot + $instot;
    
    # sort into highest to lowest
    my @sorted = sort {$b->{'Total'} <=> $a->{'Total'}}  @adv;

    # clear up collections
    undef @adv;
    undef %advisers;

I thought it was worth doing as I then have to sort the results which created yet another array, so I think keeping array clones to a minimum is a good idea even though I was clearing them out the second they weren't needed!

"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"

Free Dance Music Downloads
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top