slayedbylucifer
slayedbylucifer

Reputation: 23532

Delete an array element completely

Goal: Remove a particular Value from an array

I have written a script and it works fine but I am not happy the way I have written it. So I am curious to know is there a better way to write it. please consider below use case:

I have a nested hash/hash/array...like below. I need to remove any array values which has local in their name:

#!/usr/bin/perl -w
use strict;
use Data::Dumper;

my $hash = { esx1 => 
                    { cluster   => "clu1",
                      fd        => "fd1",
                      ds        => [ 
                                        'ds1',
                                        'ds2',
                                        'localds',
                                    ],  
                    },  
            esx2 => 
                    { cluster   => "clu2",
                      fd        => "fd2",
                      ds        => [ 
                                        'ds3',
                                        'ds4',
                                        'dslocal',
                                    ],  
                    },  
            };  


foreach my $a ( keys %$hash )
{
    foreach ( 0..$#{ $hash->{$a}->{ds} } ) 
    {   
        delete $hash->{$a}->{ds}->[$_] if $hash->{$a}->{ds}->[$_] =~ /local/i;
        @{ $hash->{$a}->{ds} } = grep defined, @{ $hash->{$a}->{ds} };
    }   
}

print Dumper ($hash);

so the script deletes the "localds" and "dslocal" and keeps everything else intact.

Question:

  1. Is there a cleaner way to write the foreach ( 0..$#{$hash->{$a}->{ds} } ) loop
  2. If I do not write the grep line above, the resultant array has the value containing local deleted but is replaced by undef. Why is this happening.

Thanks.

Upvotes: 0

Views: 463

Answers (5)

beresfordt
beresfordt

Reputation: 5222

Not much neater but:

foreach my $a ( keys %$hash )
{
    my $temp;
    foreach ( @{ $hash->{$a}->{ds} } ) 
    {   
        push(@$temp, $_) unless $_ =~ /local/i;
    }
    $hash->{$a}->{ds} = $temp;
}

Delete doesn't alter the array structure, it just alters the array content. Because of this in your method you need to grep for defined entries to create a new array of the structure you desire, then overwrite the old array.

edit: This is better explained on perldoc page for delete

delete() may also be used on arrays and array slices, but its behavior is less straightforward. Although exists() will return false for deleted entries, deleting array elements never changes indices of existing values; use shift() or splice() for that. However, if all deleted elements fall at the end of an array, the array's size shrinks to the position of the highest element that still tests true for exists(), or to 0 if none do.

edit:

As pointed out by mob splice will do what you want:

foreach my $a ( keys %$hash )
{
    for(0..$#{ $hash->{$a}->{ds} } ) 
    {   
        splice( @{ $hash->{$a}->{ds} }, $_, 1 ) if $hash->{$a}->{ds}->[ $_ ] =~ /local/i;
    }
}

Upvotes: 1

Oleg V. Volkov
Oleg V. Volkov

Reputation: 22481

Why first iterate through array and delete elements and then look for "not-deleted" nodes (side note - this grep should be outside loop)? You can look for good nodes from very start! Replace entire loop with:

foreach my $a ( keys %$hash )
{
    @{ $hash->{$a}->{ds} } = grep { !/local/i } @{ $hash->{$a}->{ds} };
}

Upvotes: 2

FMc
FMc

Reputation: 42421

for my $h (values %$hash){
    $h->{ds} = [ grep { $_ !~ /local/i } @{$h->{ds}} ];
}

Upvotes: 1

LeoNerd
LeoNerd

Reputation: 8542

delete is for elements in hashes. It happens to work on arrays by a quirk of implementation, but it shouldn't be relied on.

For arrays, you want to use splice.

splice @{ $ref->{to}->{array} }, $index, 1, ();

This replaces the 1-element sublist starting at $index with (), the empty list.

Upvotes: 5

jshy
jshy

Reputation: 111

You could use List::MoreUtils qw/first_idx/ to get the index of /local/ like so

first_idx { $_ =~ /local/i } @{$hash->{$a}->{ds}};

Then do what you want with that.

Upvotes: 0

Related Questions