Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

inverting hash / grouping values

by LanX (Saint)
on Sep 27, 2013 at 14:21 UTC ( [id://1055985]=perlquestion: print w/replies, xml ) Need Help??

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

hi

a frequent question in hash and list manipulation leads to grouping of values in sub arrays.

(This week had already two questions which lead to push @{ $h2{$v} } , $k; constructs.)

For instance inverting (i.e. w/o loosing repeated values like with reverse ) of a hash can be done like this

DB<122> sub invert { my ($h)=@_; my %h2; while ( my ($k,$v) = each %$h ) { push @{ $h2{$v} } , $k; } return %h2; } DB<123> @t{a..f}=(1,2)x3 => (1, 2, 1, 2, 1, 2) DB<124> \%t => { a => 1, b => 2, c => 1, d => 2, e => 1, f => 2 } DB<125> invert \%t => (1, ["e", "c", "a"], 2, ["b", "d", "f"])

I took a look into List::MoreUtils and other hash related modules w/o finding a similar solution, even when chaining two or more functional constructs. ( no part from List::MoreUtils doesn't really do )

Do you know any better solutions?

Cheers Rolf

( addicted to the Perl Programming Language)

Replies are listed 'Best First'.
Re: inverting hash / grouping values
by Athanasius (Archbishop) on Sep 27, 2013 at 15:02 UTC

    Have you looked at the safe_reverse function in Hash::MoreUtils?

    #! perl use strict; use warnings; use Data::Dump; use Hash::MoreUtils qw(safe_reverse); my %t = ( a => 1, b => 2, c => 1, d => 2, e => 1, f => 2 ); dd \%t; my %dup_rev = safe_reverse \%t; dd \%dup_rev;

    Output:

    0:56 >perl 729_SoPW.pl { a => 1, b => 2, c => 1, d => 2, e => 1, f => 2 } { 1 => ["c", "e", "a"], 2 => ["b", "d", "f"] } 0:57 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      > Have you looked at the safe_reverse function in Hash::MoreUtils?

      Yes I looked - but I didn't install to try ... hmm thanks!

      IMHO the documentation is quite misleading ...

      Returns safely reversed hash (value, key pairs of original hash). If no BLOCK is given, following routine will be used:

      (value,key pairs ???)

      ... and better examples showing in and output are lacking.

      After looking at it I was unhappy about the blocked namespace, a module called Hash::MoreUtils could offer much more.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      update

      After diving into the code, it looks like safe_reverse will not represent unique values as 1-element-arrays but as scalars.

      update

      This partly explains my confusion, this function works like a normal reverse if all values are unique.

      DB<100> use Hash::MoreUtils qw/safe_reverse/ DB<101> @h{a..c}=1..3 => (1, 2, 3) DB<102> safe_reverse \%h => (1, "a", 3, "c", 2, "b") DB<105> @h{a..c}=(1,1,2) => (1, 1, 2) DB<106> safe_reverse \%h => (1, ["a", "b"], 2, "c")

      to be able to have the same effect like invert in the OP one needs to write

      DB<110> safe_reverse sub { my ($k, $v, $r) = @_; return [ @{$r->{$v} +}, $k ] },\%h => (1, ["a", "b"], 2, ["c"])
        DB<106> safe_reverse \%h => (1, ["a", "b"], 2, "c")

        That kind of hash (reference) drives me nuts as it makes using the values cumbersome due to the need to test the type before use. (That reminds me to write a utility sub to convert (plain) scalar to one-element array reference.)

        I'm confused by your question. I have seen a key and its value frequently refered to as a key/value pair.

        Do I misunderstand you?

Re: inverting hash / grouping values
by hdb (Monsignor) on Sep 27, 2013 at 15:00 UTC

    It can be done this way

    my %ih = map { my $k = $_; $k => [ grep { $h{$_} eq $k } keys %h ] } k +eys {reverse %h};

    but it must be much slower than the simple code you have shown. Using keys and reverse to get the list of the unique values of the hash and then repeatedly grep on the keys of the original hash must be rather slow.

      Thanks, but I wasn't clear enough!

      I would like to see an elegant solution, that is w/o deeply nested constructs like map within map.

      IMHO one of the striking points of chaining simple functions is better readability compared to nested solutions.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        Next attempt:

        use strict; use warnings; use List::Util 'reduce'; use List::MoreUtils 'mesh'; use Data::Dumper; my %t = ( a => 1, b => 2, c => 1, d => 2, e => 1, f => 2, g => 3 ); my( $k, $v ) = @{ +reduce { if( $a->[0]->[-1] eq $b->[0]->[0] ) { push @{$a->[1]->[-1]}, $b->[1]->[0]->[0]; } else { push @{$a->[0]}, $b->[0]->[0]; push @{$a->[1]}, [$b->[1]->[0]->[0]]; } $a; } map { [ [$t{$_}], [[$_]] ] } sort { $t{$a} <=> $t{$b} } keys %t } +; my %i = mesh @$k, @$v; print Dumper \%i;
Re: inverting hash / grouping values
by kcott (Archbishop) on Sep 28, 2013 at 15:10 UTC

    G'day LanX,

    Here's a couple more to throw into the mix for you to reject. :-)

    #!/usr/bin/env perl use strict; use warnings; my %h = (a => 1, b => 2, c => 1, d => 2, e => 1, f => 2); my %i; $i{$h{$_}}[@{ $i{$h{$_}} }] = $_ for keys %h; my %j = do { $_{$h{$_}}[@{$_{$h{$_}}}] = $_ for keys %h; %_ }; use Data::Dump; dd \%h, \%i, \%j;

    Output:

    $ pm_hash_invert.pl ( { a => 1, b => 2, c => 1, d => 2, e => 1, f => 2 }, { 1 => ["a", "e", "c"], 2 => ["f", "b", "d"] }, { 1 => ["a", "e", "c"], 2 => ["f", "b", "d"] }, )

    -- Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1055985]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (5)
As of 2024-03-28 17:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found