Reputation: 23532
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:
foreach ( 0..$#{$hash->{$a}->{ds} } )
loop 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
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
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
Reputation: 42421
for my $h (values %$hash){
$h->{ds} = [ grep { $_ !~ /local/i } @{$h->{ds}} ];
}
Upvotes: 1
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
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