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

I've never figured out a concise way to do this. I have a hash and I want to seek certain values, and ALSO see the keys for those values, like:
DB<1> x grep /cat/, values %h
I see my values, but I ALSO want to see each corresponding key. Obviously it's a simple matter to write a for(){} to do this, but I'm thinking there is some elusive grep that can do it using the hash in list context? Oh maybe "each"..

My spidey sense says it in there somewhere.. Happy Friday the 13th team Perl!

Replies are listed 'Best First'.
Re: How to grep for values & see the keys?
by LanX (Saint) on May 13, 2022 at 13:46 UTC
    TIMTOWTDI!

    But instead of reinventing the wheel, there is a pairgrep and pairmap in (newer°) List::Util, which is core

    debugger-demo (via perl -de0 )

    DB<10> @h{1..26} = a..z DB<11> use List::Util qw/pairgrep/ DB<12> x pairgrep { $b =~ /[aeiou]/ } %h 0 15 1 'o' 2 21 3 'u' 4 9 5 'i' 6 5 7 'e' 8 1 9 'a' DB<13> use List::Util qw/pairmap/ DB<14> x pairmap { $b =~ /[aeiou]/ ? [$a => $b] : () } %h 0 ARRAY(0x349eed8) 0 15 1 'o' 1 ARRAY(0x349ef20) 0 21 1 'u' 2 ARRAY(0x349ef80) 0 9 1 'i' 3 ARRAY(0x349efe0) 0 5 1 'e' 4 ARRAY(0x349f040) 0 1 1 'a' DB<15>

    update

    Perl doesn't have datatype for pairs, but you could use a second hash for shorter syntax :)

    DB<15> %h2 = pairgrep { $b =~ /[aeiou]/ } %h DB<16> x \%h2 0 HASH(0x349ee60) 1 => 'a' 15 => 'o' 21 => 'u' 5 => 'e' 9 => 'i' DB<17>

    update

    °) came into core with 5.20.0 i.e. since May 27, 2014. So only new for oldies like me ;-)

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

      Rolf as the song goes- YOU ARE SIMPLY THE BEST.. Whenever I see your name in a reply, I rejoice!

      Curiously, I was on the right track because I was looking at List::Util qq(pairs) to solve this but it only seemed to work on arrays. I didn't see pairgrep , looks very useful. I'll try that today after studying your example.

Re: How to grep for values & see the keys?
by tybalt89 (Monsignor) on May 13, 2022 at 17:18 UTC

    Using 'map' as a 'grep' :)

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11143876 use warnings; my %hash; @hash{1..26} = 'a' .. 'z'; my %want = map { $hash{$_} =~ /c|x/ ? ($_, $hash{$_}) : () } keys %has +h; use Data::Dump 'dd'; dd \%want;

    Outputs:

    { 3 => "c", 24 => "x" }
Re: How to grep for values & see the keys?
by hippo (Archbishop) on May 13, 2022 at 14:02 UTC

    It's an FAQ! The first solution proposed there is pretty concise in terms of keystrokes, if that's what you really want.


    🦛

      Yes as I said there are many ways to do this in a while or for... That's what I was trying to avoid. But TYVM for your kind reply!

        Indeed - that's why I drew particular attention to the first solution in the FAQ which uses neither a while nor a for.


        🦛

Re: How to grep for values & see the keys?
by kcott (Archbishop) on May 14, 2022 at 01:31 UTC

    G'day misterperl,

    Hash keys are unique; their values do not have this restriction. Unless you can guarantee unique values, for the entire life of your code, do not use solutions that rely on unique values (e.g. reverse). Consider the different output from these two runs of identical code:

    $ perl -e ' use Data::Dump; my %h = qw{cat Tibbles dog Rover kitten Tibbles}; my %r = reverse %h; dd \%h; dd \%r; ' { cat => "Tibbles", dog => "Rover", kitten => "Tibbles" } { Rover => "dog", Tibbles => "cat" } $ perl -e ' use Data::Dump; my %h = qw{cat Tibbles dog Rover kitten Tibbles}; my %r = reverse %h; dd \%h; dd \%r; ' { cat => "Tibbles", dog => "Rover", kitten => "Tibbles" } { Rover => "dog", Tibbles => "kitten" }
    "I've never figured out a concise way to do this. ... Obviously it's a simple matter to write a for(){} to do this, but I'm thinking there is some elusive grep that can do it using the hash in list context?"

    You may be falling into the trap of thinking shorter code is faster code. In the main, for is faster than grep and map. I've put together a Benchmark example to show this. I haven't used pairmap or pairgrep previously, so a added a pairmap to the benchmark (which turned out to be even slower). Do note that this is just an example; you should modify this to more accurately reflect the code you're working with.

    #!/usr/bin/env perl use strict; use warnings; use Benchmark 'cmpthese'; use List::Util 'pairmap'; use Data::Dump; my %h = qw{mammal cat mollusc snail fish cat}; print "*** Check initial data\n"; dd \%h; print "*** Check subs return the same value\n"; print "_for\n"; dd _for(); print "_map\n"; dd _map(); print "_pairmap\n"; dd _pairmap(); print "*** Benchmark\n"; cmpthese 0 => { for => \&_for, map => \&_map, pairmap => \&_pairmap, }; sub _for { my $result = []; for my $key (keys %h) { next unless $h{$key} eq 'cat'; push @$result, $h{$key}, $key; } return $result; } sub _map { my $result = []; @$result = map { $h{$_} eq 'cat' ? ($h{$_}, $_) : () } keys %h; return $result; } sub _pairmap { my $result = []; @$result = pairmap { $b eq 'cat' ? ($b, $a) : () } %h; return $result; }

    The output from the initial checks was identical on each run, so I'll just show that once.

    *** Check initial data { fish => "cat", mammal => "cat", mollusc => "snail" } *** Check subs return the same value _for ["cat", "fish", "cat", "mammal"] _map ["cat", "fish", "cat", "mammal"] _pairmap ["cat", "fish", "cat", "mammal"]

    I discarded the output of the first benchmark run because the initial loading of modules can skew the results. Here's the results of the next three runs:

    *** Benchmark Rate pairmap map for pairmap 775527/s -- -33% -42% map 1154992/s 49% -- -14% for 1346695/s 74% 17% -- *** Benchmark Rate pairmap map for pairmap 779518/s -- -34% -43% map 1177598/s 51% -- -13% for 1360223/s 74% 16% -- *** Benchmark Rate pairmap map for pairmap 781507/s -- -34% -43% map 1178381/s 51% -- -14% for 1364313/s 75% 16% --

    — Ken