con
con

Reputation: 6113

Perl: How to grep with Hash Slices?

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

Answers (1)

brian d foy
brian d foy

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

Related Questions