×
INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!
  • Students Click Here

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Jobs

using shift on current array index within a foreach
2

using shift on current array index within a foreach

using shift on current array index within a foreach

(OP)
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

RE: using shift on current array index within a foreach

Hi

Quote (1DMF)

Is it possible to loop an array and remove the current item with shift?
Certainly not with shift, as it always removes the first element of the array.

As you called shift without parameter, it not removes from @arr, but from @ARGV :

CODE --> Perl

@ARGV = qw{ a1 a2 a3 };

my @arr = (1,2,3,4,5,6,7,8,9);
foreach my $item (@arr)
{
  shift if $item == 5;
}
print "\@arr : @arr\n";

print "\@ARGV : @ARGV\n"; 

CODE --> output

@arr : 1 2 3 4 5 6 7 8 9
@ARGV : a2 a3 

To remove any element by its index, you can use splice. ( You can also use delete, 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 grep :

CODE --> Perl

@arr = grep { $_ != 5 } @arr;

# or

@arr = grep !/^5$/, @arr; 

Quote (1DMF)

Is it possible and is it advisable altering an array while looping it?
Not directly, as in Perl foreach can not assign both the current index and value to loop control variables. So you will end with something like this :

CODE --> Perl

my @arr = (1,2,3,4,5,6,7,8,9);
foreach my $item (keys @arr)
{
  splice @arr, $item, 1 if $arr[$item] == 5;
}
print "\@arr : @arr\n"; 
While that seems to work, if you turn on warnings, you will get this, which smells like not advisable :

Quote (use warnings;)

Use of uninitialized value within @arr in numeric eq (==) at /home/master/1DMF.pl line 6.

Feherke.
feherke.github.io

RE: using shift on current array index within a foreach

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

CODE --> 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"; 

RE: using shift on current array index within a foreach

(OP)
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

RE: using shift on current array index within a foreach

Hi

Quote (1DMF)

How come you are using the 'keys' reserved word on an array?
Sorry, forgot the warning about the version requirement :

Quote (man perlfunc)

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.

Quote (1DMF)

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.

Quote (1DMF)

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 for's 2nd expression ( $i <= $#arr ) 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, redo just executes for's instruction block again, without executing for's 3rd expression ( $i++ ). 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 :

CODE --> Perl

for (my $i = $#arr; $i >= 0; $i--) {
  splice @arr, $i, 1 if $arr[$i] eq 'd';
} 

Feherke.
feherke.github.io

RE: using shift on current array index within a foreach

(OP)
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

RE: using shift on current array index within a foreach

Hi

Quote (1DMF)

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 keys 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

RE: using shift on current array index within a foreach

(OP)
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

Red Flag This Post

Please let us know here why this post is inappropriate. Reasons such as off-topic, duplicates, flames, illegal, vulgar, or students posting their homework.

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Reply To This Thread

Posting in the Tek-Tips forums is a member-only feature.

Click Here to join Tek-Tips and talk with other members! Already a Member? Login

Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close