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

I found out that I left some unaltered code that I programmed 10 years ago and it went unnoticed. I knew this bug and I actually fixed it quick and dirty in multiple places but I never found a good fix for it.

What is the best way to use the splice inside of a loop like this (foreach, or other)

Original code bugged:

sub filterExclude{ my $ar = $_[0] ; # REF! my $someConfig = $_[1] ; my $ix = 0 ; foreach ( @{$ar} ) { my $sKey = $_ ; if ( exists ( $someConfig ->{ $sKey }->{ Exclude} ) && $someConf +ig ->{ $sKey }->{ Exclude} ) { splice ( @{$ar}, $ix, 1 ) ; $ix = $ix - 1 ; } $ix = $ix + 1 ; } }

Right now I have:

sub filterExclude{ # 20240414 Quick fix needed because of the splice my $ar = $_[0] ; # REF! my $someConfig = $_[1] ; my $tmpCheckedEverything = 0 ; while ( !($tmpCheckedEverything) ) { $tmpCheckedEverything = 1 ; my $ix = 0 ; foreach ( @{$ar} ) { my $sKey = $_ ; if ( exists ( $someConfig ->{ $sKey }->{ Exclude} ) && $someC +onfig ->{ $sKey }->{ Exclude} ) { splice ( @{$ar}, $ix, 1 ) ; $tmpCheckedEverything = 0 ; } $ix = $ix + 1 ; } }

But of course this is ugly and not efficient

Replies are listed 'Best First'.
Re: How to use a splice inside a foreach
by hv (Prior) on Apr 13, 2024 at 11:14 UTC

    The usual way to cope with the array changing while you iterate over it is to go in reverse, so that the splice does not affect any not-yet-processed index. It's easiest to iterate over the index rather than the value:

    for my $ix (reverse 0 .. $#$ar) { if ($someConfig->{ $ar->[$ix] }{Exclude}) { splice @$ar, $ix, 1; } }

    Another approach is to fully replace the contents of the arrayref using a grep:

    @$ar = grep !$someConfig->{$_}{Exclude}, @$ar;

      Sorry for the late reply, for some reason perlmonks was not responding for days.

      Thanks, hv, tybalt89, LanX and The_Dj for your great suggestions, I can use one of these.

      PS, indeed, the exists was wrong as well. not exists ( $someConfig->{$_} ) was supposed to prevent the autovivication of the key in $someConfig.

Re: How to use a splice inside a foreach
by tybalt89 (Monsignor) on Apr 13, 2024 at 17:40 UTC

    And here's a complete run-able grep version with some tweaks.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11158852 use warnings; sub filterExclude { my ($ar, $someConfig) = @_ ; @$ar = grep { not exists ( $someConfig->{$_} ) && $someConfig->{ $_ }{ Exclude} } @$ar; } my @ar = 1 .. 10; my %config; $config{$_}{Exclude}++ for 3, 8; filterExclude( \@ar, \%config ); use Data::Dump 'dd'; dd { ar => \@ar, config => \%config };

    which Outputs:

    { ar => [1, 2, 4 .. 7, 9, 10], config => { 3 => { Exclude => 1 }, 8 => { Exclude => 1 } }, }
Re: How to use a splice inside a foreach
by LanX (Saint) on Apr 13, 2024 at 09:01 UTC
Re: How to use a splice inside a foreach
by The_Dj (Scribe) on Apr 18, 2024 at 02:18 UTC
    I think you probably want to use grep.

    sub filterExclude{ my $ar = $_[0] ; # REF! my $someConfig = $_[1] ; $ar=[ grep { exists($someConfig->{$_}{Exclude}) && $someConfig->{$_}{Exclude} } @$ar ]; return $ar }

    Note that this won't alter the original array. (That is bad practice anyway). Best to rather call as $data=filterExclude($data,$config);

    On a side note, if $someConfig->{$key} does not exist, it will be created even with the exists check.
    At the same time, $someConfig->{$key}{Exclude} wouldn't be created even without the exists check.

    Either install and use no autovivification; and just use $someConfig->{$_}{Exclude} as the test...
    or change your test to exists ($someConfig->{$_}) && $someConfig->{$_}{Exclude}

    HTH