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

Hello Monks I'm new in perl and i would like your help with this
i have a hash like this
%hash( AA=>1 AB=>5 AC=>7 AD=>3 AE=>4 ... ... ... BA=>2 BB=>4 BC=>16 BD=>21 ... ... ... ... ZA=>12 ZB=>1 ZC=>3 ... ... ZY=>2 ZZ=>2);
the problem is that it has the keys i want but also the reverse of them with different values.
I want a hash containing the unique keys (not the reversed ones) and as values the summing of them.
For eg the key AB and the key BA is the same for me.Iwant the new hash to contain as a key the AB and as value the number 7 (AB=5),(BA=2)
any idea please?

Replies are listed 'Best First'.
Re: confused with a hash which contains reversed keys
by ikegami (Patriarch) on Dec 03, 2007 at 13:23 UTC

    The easiest way is to do it correctly from the beginning.

    sub get_key { join '', sort $_[0] =~ /./g } my %hash; while (<DATA>) { chomp; $hash{get_key($_)}++; } use Data::Dumper; print(Dumper(\%hash)); __DATA__ AB AB AB AC AC BA BA BA CA
    $VAR1 = { 'AC' => 3, 'AB' => 6 };

    But the same method will allow you to fix your hash. (Added)

    sub get_key { join '', sort $_[0] =~ /./g } my %hash; while (<DATA>) { chomp; $hash{$_}++; } my %fixed; foreach my $key (keys %hash) { my $val = $hash{$key}; $fixed{get_key($key)} += $val; } use Data::Dumper; print(Dumper(\%fixed)); __DATA__ AB AB AB AC AC BA BA BA CA

    By the way, this is a moderated site, so your post may not appear instantly. Please don't triple post.

      <nitpick>If all of the OP's hash keys consist of two letters, as in the example data, your code works fine. However, nothing in the 'spec' indicates that this condition is always true. What if the hash is like:

      %hash = ( CAB => 31, BAC => 11, ABC => 42, );

      </nitpick>

        At best, it works great for that hash. At worse, you'd have to change the sort criteria.

        sub get_key { my $key1 = $_[0]; my $key2 = reverse $key2; return (sort $key1, $key2)[0]; }

        Either way, the concept I proposed works fine.

Re: confused with a hash which contains reversed keys
by moritz (Cardinal) on Dec 03, 2007 at 13:18 UTC
    You can iterate over the hash, and build a new one:
    my %uniq; for (keys %hash){ if (exists $uniq{scalar reverse $_}){ $uniq{scalar reverse $_} += $hash{$_}; } else { $uniq{$_} = $hash{$_}; } } # untested

    Note that the outcome will either contain the key 'AB' or 'BA', but you don't have control over which one occurs (it's not in the spec ;-)

      Note that the outcome will either contain the key 'AB' or 'BA', but you don't have control over which one occurs
      So sort the keys before adding...
      my %uniq; for ( keys %hash ) { my $reordered_key = join sort split ''; $uniq{$reordered_key} += $hash{$_}; } # still untested

      The alphabetically first one will be used (ie AB, rather than BA even if AB doesn't occur).

      update: Fixed ikegami's failing test case

        Sorting like that won't produce consistent results. If EA is present in the original hash but not AE, you'll end up with EA instead of AE. Sort the chars of the key rather than the order in which they are fetched to avoid guessing under which key a particular value is located.
        thanks a lot for your help!It works correctly
Re: confused with a hash which contains reversed keys
by Joost (Canon) on Dec 03, 2007 at 13:21 UTC
      There is a ton of similarity between this hash and a symmetric matrix. So, the choice of which symmetric element to be the right one and the reversed one is arbitrary. Since the indices are alphabetical, then alphabetical order makes the most sense. Use the alpha pair where the first letter is earlier in the alphabet. ikegami's solution below seems to do that nicely.

Re: confused with a hash which contains reversed keys
by dwm042 (Priest) on Dec 03, 2007 at 19:47 UTC
    This may not be as slick as other answers, but I thought I'd offer it. This one is a "fix up" code, as opposed to maintenance code.

    #!/usr/bin/perl use warnings; use strict; my %hash = ( AA=>1, AB=>5, AC=>7, AD=>3, AE=>4, BA=>2, BB=>4, BC=>16, BD=>21, ZA=>12, ZB=>1, ZC=>3, ZY=>2, ZZ=>2, ); my %newhash=(); for ( sort keys %hash ) { next if defined $newhash{sym_index($_)}; if ( defined($hash{reverse($_)}) ) { $newhash{sym_index($_)} = $hash{$_} + $hash{reverse ($_)}; } else { $newhash{sym_index($_)} = $hash{$_}; } } for ( sort keys %newhash ) { print $_, " => ", $newhash{$_}, "\n"; } sub sym_index { my $a = shift; return $a if ( $a le reverse( $a ) ); return reverse( $a ); }
    And results are:

    C:\Code>perl symmetrize.pl AA => 2 AB => 7 AC => 7 AD => 3 AE => 4 AZ => 12 BB => 8 BC => 16 BD => 21 BZ => 1 CZ => 3 YZ => 2 ZZ => 4
    Update: cleaner output
      i think that your code has a bug...
      look carefylly the AA and ZZ these keys have not the correct values(the values are doupled)
        if you use that the code is ok if ( defined($hash{reverse($_)})and $hash{reverse($_)} ne $hash{$_} )
Re: confused with a hash which contains reversed keys
by Anonymous Monk on Dec 03, 2007 at 23:34 UTC
    for my $key ( sort keys %hash ) { my $rev = reverse $key; $hash{ $rev } += delete $hash{ $key } if $rev ne $key; }