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

Hi Monks!

From this simple code example, how could I get all the keys from the value "red"? I thought using the "grep" command I could get the "apple" and "kiwi" from the hash, only getting "kiwi".
Any simple suggestions?
#!/usr/bin/perl my $search = "red"; my %fruit = ( 'apple' => ['red','green'], 'kiwi' => 'green', 'banana' => 'yellow', ); my ($match) = grep { $fruit{$_} eq $search } keys %fruit; print "\n\n Match = $match\n\n";

Thank you!

Replies are listed 'Best First'.
Re: Find all keys from a value in a hash
by haukex (Archbishop) on Nov 09, 2021 at 21:16 UTC

    There's More Than One Way To Do It: If you need to do a lot of lookups, then grep will quickly become inefficient, and instead it will likely be worth it to build a reverse lookup table (assuming it fits comfortably into memory). With the following, you can simply write $colors{green}, $colors{red}, etc.

    use warnings; use strict; my %fruit = ( apple => ['red','green'], kiwi => 'green', banana => 'yellow', ); my %colors; for my $f (keys %fruit) { for my $c ( ref $fruit{$f} ? @{ $fruit{$f} } : $fruit{$f} ) { push @{ $colors{$c} }, $f; } } use Data::Dump; dd \%colors;

    By the way, always Use strict and warnings!

Re: Find all keys from a value in a hash
by Fletch (Bishop) on Nov 09, 2021 at 20:55 UTC

    You might double check your data because a) $fruit{kiwi} is "green" not "red", and b) matching an arrayref as is in $fruit{apple} against a string value using eq isn't going to do anything useful (it will be checking that the stringified representation of the array reference is your target, which it's most likely not).

    Addendum: I'm guessing you were actually searching for "green" but you mucked up your data, in which case you would have found the one value in $fruit{kiwi}. Your problem then is that if your value is an array ref you'd need to again grep the values therein. WHile you can handle this with something like below:

    my( @found ) = grep { ( ref $fruit{ $_ } eq 'ARRAY' ? (grep { $_ eq $s +earch } @{ $fruit{ $_ } } ) : $fruit{$_} eq $search ) } keys %fruit;

    Cleaner (but slightly less memory efficient) would be to always store an arrayref even for single items; then the code would collapse to just:

    my( @found ) = grep { grep $_ eq $search, @{ $fruit{ $_ } } } keys %fr +uit;

    Edit: Also another problem since you're catching the result of the grep with my( $found ) you're only going to see the first value returned from grep (any further matches will be silently discarded because you've only got the one scalar variable named in the list-context my declaration); you need to use an array instead if you want the list of all the matches found.

    Another thought:, If your goal is to have something where you can check "What fruits can be color x?" then using a hash of hashes (HoH) can reduce this to a simple grep { exists $fruits{ $_ }->{$color} } keys %fruits. You'd need to set up %fruits a bit differently.

    my %fruits = ( apple => { red => 1, green => 1 }, kiwi => { green => 1 }, banana => { yellow => 1 } );

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Yes, sorry, my bad, I meant "green"!!!
      Actually the idea is to have a hash look up table to translate the values, that's why the sample code, I will have in a sub routine and each item will be passed and matched, then the key returned.
Re: Find all keys from a value in a hash
by LanX (Saint) on Nov 09, 2021 at 20:58 UTC
    one of the rare cases where smartmatch is really helpful.

    Note the warning, so not meant for production code.

    use strict; use warnings; my %fruit = ( 'apple' => ['red','green'], 'kiwi' => 'green', 'banana' => 'yellow', ); for my $search ("green","red" ){ my @matches = grep { $search ~~ $fruit{$_} } keys %fruit; print "\n\n Matches for <$search> = @matches\n\n"; }

    C:/Strawberry/perl/bin\perl.exe -w d:/tmp/pm/smartmatch.pl Smartmatch is experimental at d:/tmp/pm/smartmatch.pl line 12. Matches for <green> = kiwi apple Matches for <red> = apple Compilation finished at Tue Nov 9 22:04:30

    UPDATE

    allowed multiple matches

    update

    seriously, your data structure is misdesigned . If you want singular elements put them into an array too.

    my %fruit = ( 'apple' => ['red','green'], 'kiwi' => ['green'], 'banana' => ['yellow'], );

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: Find all keys from a value in a hash
by perlfan (Parson) on Nov 10, 2021 at 16:37 UTC
    You basically need to maintain a membership map as an index of values, the reverse look up referred to below:
    my $search = "red"; my %fruit = ( 'apple' => ['red','green'], 'kiwi' => 'green', 'banana' => 'yellow', ); my %idx_fruit = ( 'red' => {'apple' => 1 }, 'green' => {'apple' => 1, 'kiwi' => 1 }, 'yellow' => {'banana' => 1}, );
    Note: %idx_fruit is a hash, but contains members as hash references. This is to provide natural semantics for managing the membership sets and accessing values (e.g., delete, exists, etc). The value => 1 is meaningless, but might be useful for some additional meta data. You could even have them point to the reference of the member in %fruit for maximized fun.
      I came up with this:
      #!/usr/bin/perl use strict; use warnings; my $search = $ARGV[0] || ''; if(!$search) { print "\n\n Need a color word to search! \n\n"; exit; } my %fruit = ( 'apple' => ['red','green'], 'kiwi' => ['blue'], 'banana' => ['yellow'], 'avocato' => ['brown'], ); my ($result) = grep { grep { $_ eq $search } @{$fruit{$_}} } keys %fru +it; print "\n\n Result:::: --> $result \n\n";
        One thing that I like about this approach is to use anon ref's to array for all of the values if any key can be associated with more than a single color. This eliminates the checking of "if ref".