Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

perl - operations allowed on array while iterating through it

What are the operations allowed on array, while iterating through it? Is it possible to shift/unshift, pop/push, delete elements without confusing the iterator?

Is that any different for adding/removing key-value pair from hash?

Thank you for your help.

like image 275
mjp Avatar asked Mar 18 '23 02:03

mjp


2 Answers

You can assign to existing elements, but should not add or remove them. So no shift, unshift, pop, push, or splice. perlsyn:

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.

If you are iterating over a hash with each, you should also avoid adding or removing elements, except that you are explicitly allowed to remove the current element. each:

If you add or delete a hash's elements while iterating over it, the effect on the iterator is unspecified; for example, entries may be skipped or duplicated--so don't do that. Exception: It is always safe to delete the item most recently returned by each(), so the following code works properly:

But as it says, the worst that could happen is entries being skipped or duplicated; modifying an array you are looping over, on the other hand, can lead to segfaults.

like image 168
ysth Avatar answered Apr 30 '23 10:04

ysth


As ysth has already pointed out, it is unwise to attempt to modify an array while iterating directly on its elements.

However, if one does want to modify an array dependent on the element values, the trick is to do it in reverse index order.

For example, say I have an array of numbers. I would like modifier the array so that every multiple of 4 has a string inserted after it, and every multiple of 5 is removed. I would accomplish that using the following:

use strict;
use warnings;

my @array = ( 1 .. 20 );

for my $i ( reverse 0 .. $#array ) {
    # Insert after multiples of 4
    if ( ( $array[$i] % 4 ) == 0 ) {
        splice @array, $i + 1, 0, "insert";
    }

    # Remove multiples of 5
    if ( ( $array[$i] % 5 ) == 0 ) {
        splice @array, $i, 1;
    }
}

use Data::Dump;
dd @array;

Outputs:

(
  1 .. 4,
  "insert",
  6,
  7,
  8,
  "insert",
  9,
  11,
  12,
  "insert",
  13,
  14,
  16,
  "insert",
  17,
  18,
  19,
  "insert",
)

Alternatively, if you want to transform an array, it's also possible to use map like so:

my @newarray = map {
    (   ( ($_) x !!( $_ % 5 ) ),         # Remove multiples of 5
        ( ('insert') x !( $_ % 4 ) ),    # Insert After multiples of 4
        )
} ( 1 .. 20 );

use Data::Dump;
dd @newarray;
like image 35
Miller Avatar answered Apr 30 '23 12:04

Miller