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

Dear Monks,
I have a simple hash where keys are text and values are anonymous array of numbers. I like to reverse the hash. Making a string of numbers (from the orginal hash value) sorted and seperated by space as key and the original hash key as value. To gurantee the uniquness of the hash keys, I like to drop as Many as numbers possible from the array of numbers before forming the string of numbers.

Your suggestions would be helpful.
Thanks
artist

Replies are listed 'Best First'.
Re: Reversing Hash
by tachyon (Chancellor) on Mar 03, 2003 at 09:32 UTC

    my %new = map { (join ' ', @$old{$_}), $_ } keys %old;

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Reversing Hash
by Anonymous Monk on Mar 03, 2003 at 09:29 UTC

    How does dropping numbers from the array help guarentee uniqueness?

    On what basis do you decide it is possible to drop numbers?

    Because they are duplicates in that array?

    You need to clarify this.

      Within single array there are no duplicates. But assume that there are only 4 arrays.
    • [1, 20, 3], [1, 30, 4, 7], [1,9,8] and [1, 9, 17]

      My new hash keys would be

      1. "3"
      2. "4"
      3. "1 8"
      4. "1 17"

      Which I can match one to one with original arrays.

      Thanks, artist.

        Hi.

        This solution assumes that you don't mind duplicate values (i.e. where two unique keys from %hash have the same value) being pushed onto an array.

        Also, feel free to change the value of $; to whatever you like if a space isn't suitable.

        use strict; my %rev_hash = (); my %hash = ( 'key1' => [1, 20, 3], 'key2' => [1, 30, 4, 7], 'key3' => [1, 9, 8], 'key4' => [1, 9, 17], 'key5' => [1, 9, 17], ); { local ($;) = " "; while (my ($k, $v) = each %hash) { push @{$rev_hash{"@{$v}"}}, $k; } while (my ($k, $v) = each %rev_hash) { print qq(\$rev_hash{$k} = "@{$v}"\n); } }

        Cheers,

        -- Dave :-)


        $q=[split+qr,,,q,~swmi,.$,],+s.$.Em~w^,,.,s,.,$&&$$q[pos],eg,print
        Huhh, can you explain your algorithm for reducing the arrays a bit further?? I see at the moment plenty of possibilities, e.g. I don't see why
        1. 3
        2. 4
        3. 8
        4. 17
        might not be a valid reduction ...

        -- Hofmator

Re: Reversing Hash
by rob_au (Abbot) on Mar 03, 2003 at 10:10 UTC
    How about this straight reversal of keys and values ...

    my %hash = ( '1' => 'one', '2' => 'two', '3' => 'three' ); my %new; @new{ values %hash } = keys %hash; print Dumper( \%new );

     

    perl -le 'print+unpack("N",pack("B32","00000000000000000000001000111000"))'

      only, the values are anonymous arrays and not strings ... which leads to
      my %new; @new{ map {join ' ', @$_} values %hash } = keys %hash;
      and I think I like tachyon's straightforward map better than this ...

      -- Hofmator

Re: Reversing Hash
by artist (Parson) on Mar 03, 2003 at 10:42 UTC
    Hi Dave
    That doesn't work:
    Your script gives me answer without any 'reduction' in rev_hash keys.
    $rev_hash{1 20 3} = "key1"
    $rev_hash{1 9 17} = "key4 key5"
    $rev_hash{1 9 8} = "key3"
    $rev_hash{1 30 4 7} = "key2"
    
    Which doesn't reduce the keys to its minimum possible. I like to have keys "3" "1 17" "1 8" and "4" . so the answer should be
    $rev_hash{3} = "key1"
    $rev_hash{1 17} = "key4 key5"
    $rev_hash{1 8} = "key3"
    $rev_hash{4} = "key2"
    

    Thanks
    artist

    Update: I think question is mis-understood by most first-answeres. Above example would make it clear.

    Update2: Hofmator gave a more correct solution at Re: Re: Re: Reversing Hash as it reduces most elements in total. I overlooked that earlier and should have produced a better example.

      You'll have to do that in three steps. First step:
      my %hash = ( 'key1' => [1, 20, 3], 'key2' => [1, 30, 4, 7], 'key3' => [1, 9, 8], 'key4' => [1, 9, 17], 'key5' => [1, 9, 17], ); my %rev_hash; while(my ($k, $v) = each %hash) { push @{$rev_hash{$_}}, $k for @$v; }
      Now we have this:
      %rev_hash = (
      	'7'  => ['key2'],
      	'8'  => ['key3'],
      	'30' => ['key2'],
      	'9'  => [ 'key3', 'key4', 'key5' ],
      	'1'  => [ 'key1', 'key2', 'key3', 'key4', 'key5' ],
      	'17' => [ 'key4', 'key5' ],
      	'3'  => ['key1'],
      	'4'  => ['key2'],
      	'20' => ['key1']
      );
      Now let's lump together unique lists of values. I tried a more general approach trying to compare arrays as arrays at first, but it makes things much harder due to double bookkeeping. Since you just want the values concatenated to a string, the approach can be simplified drastically by doing that beforehand.
      $_ = join ' ', sort @$_ for values %rev_hash; my %inv_hash; while(my ($k, $v) = each %rev_hash) { push @{$inv_hash{$v}}, $k; }
      Almost all the way there:
      %inv_hash = (
      	'key1'                     => [ '3', '20' ],
      	'key3 key4 key5'           => ['9'],
      	'key2'                     => [ '7', '30', '4' ],
      	'key3'                     => ['8'],
      	'key4 key5'                => ['17'],
      	'key1 key2 key3 key4 key5' => ['1'],
      );
      We need to concatenate and reverse one final time:
      $_ = join ' ', sort {$a<=>$b} @$_ for values %inv_hash; my %revinv_hash = reverse %inv_hash;
      And now we have:
      %revinv_hash = (
      	'3 20'   => 'key1',
      	'8'      => 'key3',
      	'9'      => 'key3 key4 key5',
      	'1'      => 'key1 key2 key3 key4 key5',
      	'17'     => 'key4 key5',
      	'4 7 30' => 'key2'
      );
      There you go.

      Makeshifts last the longest.