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

Hi I have a 2D hash of arrays like: $HASH{keyA}{keyB} = [1,2,3,4]; and want to turn it into this: $HASH{keyB}{keyA} = [1,2,3,4]; and was wondering what the cleanest way to this is. thnaks!

Replies are listed 'Best First'.
Re: inverting keys in a 2-D Hash
by choroba (Cardinal) on Aug 30, 2015 at 13:59 UTC
    Just iterate over the keys on each level of the hash:
    my %h2; for my $level1 (keys %HASH) { for my $level2 (keys %{ $HASH{$level1} }) { $h2{$level2}{$level1} = $HASH{$level1}{$level2}; } } %HASH = %h2;
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: inverting keys in a 2-D Hash
by Athanasius (Archbishop) on Aug 30, 2015 at 13:54 UTC

    Hello melmoth, and welcome to the Monastery!

    I don’t know about the “cleanest” way, but delete works nicely:

    #! perl use strict; use warnings; use Data::Dump; my %HASH; $HASH{keyA}{keyB} = [1 .. 4]; dd \%HASH; my $array_ref = $HASH{keyA}{keyB}; delete $HASH{keyB}; delete $HASH{keyA}; $HASH{keyB}{keyA} = $array_ref; dd \%HASH;

    Output:

    23:53 >perl 1360_SoPW.pl { keyA => { keyB => [1 .. 4] } } { keyB => { keyA => [1 .. 4] } } 23:53 >

    Hope that helps,

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

      Maybe you should write to a new hash if you can't be sure about the risk of losing information overwriting 2D symmetries. (Imagine keys A and B being from the same set of values, like indices of a matrix)

      Afaik should delete return the deleted value, such that you can do it in a one-liner

      Otoh no need to delete when writing a new hash.

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

      delete $HASH{keyB};
      What is the point of this code line, since $HASH{keyB} probably doesn't exist?

      Personally, I would prefer, if possible, to make a copy of the original hash.

      Can’t say that I agree with you on this one:   there’s really no compelling requirement to attempt to modify the structure “in place,” and plenty of good reasons not to . . .

      Especially when we consider that “what the hashref-of-hashrefs actually contains is ...” arrayrefs.   We aren’t copying any arrays:   what we’re actually doing is creating a second (inverted ...) hashref-of-hashrefs structure, which contains references to ... the same arrays that the “old” one refers-to.   (And we are, thereafter, entirely free to keep both of these hashrefs-of-hashrefs, without penalty, since they are both effectively just “indexes” to a single collection of anonymous-arrays.)

Re: inverting keys in a 2-D Hash
by 1nickt (Canon) on Aug 30, 2015 at 14:21 UTC

    I can't imagine doing this myself after so carefully constructing a hash to index my data, but, this will do what you want:

    foreach my $key ( keys %hash ) { while ( my ( $subkey, $value ) = each %{ $hash{$key} } ) { $hash{ $subkey }{ $key } = $value; } delete $hash{ $key }; }

    But what about when there are multiple occurrences of keyB in the original hash? Or multiple subkeys in the subhashes?

    #!/usr/bin/perl use strict; use warnings; use feature qw/ say /; use Data::Dumper; my %hash; $hash{'keyA'}{'keyB'} = '1234'; $hash{'keyC'}{'keyB'} = '5678'; say Dumper \%hash; foreach my $key ( keys %hash ) { while ( my ( $subkey, $value ) = each %{ $hash{$key} } ) { $hash{ $subkey }{ $key } = $value; } delete $hash{ $key }; } say Dumper \%hash; __END__
    Output:
    $VAR1 = { 'keyA' => { 'keyB' => '1234' }, 'keyC' => { 'keyB' => '5678' } }; $VAR1 = { 'keyB' => { 'keyC' => '5678', 'keyA' => '1234' } };
    .... is that really what you want?

    The way forward always starts with a minimal test.
      Yes this is exactly what I want. I have rna sequences that have been aligned to dna so each rna has associated with it a number of dna but other rna sequences may also align to some of these dna sequences. The original keys tell me what dna is aligned to each rna but i want to see, for each dna, what rna it has been aligned to.
        #!/usr/bin/perl use strict; use warnings; use feature qw/ say /; my %hash; $hash{'rna1'}{'dna1'} = '1111'; $hash{'rna1'}{'dna2'} = '1111'; $hash{'rna2'}{'dna1'} = '2222'; $hash{'rna3'}{'dna1'} = '2222'; $hash{'rna3'}{'dna2'} = '4444'; $hash{'rna2'}{'dna3'} = '3333'; say 'Report by RNA:'; foreach my $rna ( sort keys %hash ) { say " $rna has:"; foreach my $dna ( sort keys %{$hash{$rna}} ) { say " $dna = $hash{$rna}{$dna}"; } } say ''; # reverse the hash my %hash2 = %hash; foreach my $key ( keys %hash2 ) { while ( my ( $subkey, $value ) = each %{ $hash2{$key} } ) { $hash2{ $subkey }{ $key } = $value; } delete $hash2{ $key }; } say 'Report by DNA:'; foreach my $dna ( sort keys %hash2 ) { say " $dna is in:"; foreach my $rna ( sort keys %{$hash2{$dna}} ) { say " $rna with $hash2{$dna}{$rna}"; } } say ''; __END__
        $ perl 1140433.pl Report by RNA: rna1 has: dna1 = 1111 dna2 = 1111 rna2 has: dna1 = 2222 dna3 = 3333 rna3 has: dna1 = 2222 dna2 = 4444 Report by DNA: dna1 is in: rna1 with 1111 rna2 with 2222 rna3 with 2222 dna2 is in: rna1 with 1111 rna3 with 4444 dna3 is in: rna2 with 3333 $
        The way forward always starts with a minimal test.
      Thanks everyone some good ideas