Reputation: 6113
I am trying to sort out defined parameters from a complex hash, using hash slices. Hash slices are great because they avoid lots of foreach
and if defined
so they simplify syntax greatly.
However, I'm clearly not doing this correctly:
use DDP;
my %hash = (
'a' => # first patient
{
'age' => 9,
'BMI' => 20
},
'b' =>
{
'age' => 8
}
);
my %defined_patients = grep {defined $hash{$_}{'age', 'BMI'}} keys %hash;
p %defined_patients;
The above code gives an empty hash, when I want it to return just patient a
.
This question is similar to "no autovivication" pragma fails with grep in Perl and I've based my code on it.
I've also tried
my @defined_patients = grep {defined $hash{$_}{'age', 'BMI'}} keys %hash;
but that doesn't work either.
How can I use hash slices to grep
patients with the defined keys?
Upvotes: 3
Views: 239
Reputation: 132896
If you want to check that none of the target keys are undefined, you have to check separately. This greps for undefined values:
grep { ! defined } @{ $hash{$_} }{ qw(age BMI) }
Notice that the hash slice
@{ $hash{$_} }{ qw(age BMI) }
In v5.24, you can use postfix dereferencing instead:
$hash{$_}->@{ qw(age BMI) }
But that grep
has to fit in another one. Since you want the cases where all values are defined, you have to negate the result of the inner grep
:
my @patients =
grep { ! grep { ! defined } $hash{$_}->@{ qw(age BMI) } }
keys %hash;
That's pretty ugly though. I'd probably do something simpler in a subroutine. This way you can handle any number of keys easily:
sub some_patients {
my( $hash, $keys ) = @_;
my @patient_keys;
foreach my $key ( keys %$hash ) {
next unless grep { ! defined } $hash{$key}->@{ @$keys };
push $key, @patient_keys;
}
return @patient_keys;
}
Now I simply call a subroutine instead of grokking multilevel greps:
my @patient_keys = some_patients( \%patients, [ qw(age BMI) ] );
Or, for something more targeted, maybe something like this that tests a particular sub-hash instead of the whole data structure:
sub has_defined_keys {
my( $hash, $keys ) = @_;
! grep { ! defined } $hash->@{@$keys}
}
my @target-keys = ...;
my @keys = grep {
has_defined_keys( $patients{$_}, \@target-keys )
} keys %patients;
Either way, when things start getting a bit too complex, use a subroutine to give those things names so you can hide the code in favor of something short.
Upvotes: 3