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

Hi,
Suppose I have this array:
my %hash2 =( '1' => 2, '3' => 5, '4' => 2,);
Where the key is index, and the values is the count of that index.
How can I create the new set hash, so that it gives:
$VAR1 = { '3-3' => 5, '3-4' => 10, '1-1' => 2, '3-1' => 10, '1-4' => 4, '4-4' => 2,#ends here };
My current code:
use strict; use warnings; use Data::Dumper; #print Dumper \%hash2; my %spair = (); my $prod; foreach my $si (keys %hash2) { foreach my $sn (keys %hash2) { if ($si == $sn) { $prod = $hash2{$si}; } else { $prod = $hash2{$si}*$hash2{$sn}; } $spair{$si."-".$sn} = $prod; } } print Dumper \%spair;
give this which is not as desired:
$VAR1 = { '3-3' => 5, '3-4' => 10, '1-1' => 2, '3-1' => 10, '1-4' => 4, '4-4' => 2, '4-1' => 4, #repeat '1-3' => 10,#repeat '4-3' => 10 #repeat };
As you can see it repeats 1-4 and 4-1, etc. which actually should only occur once. How can I modify my code so that we can get the expected result?

Regards,
Edward WIJAYA

Replies are listed 'Best First'.
Re: Self-Looping over hash - how to remove duplicate
by tachyon (Chancellor) on Oct 05, 2004 at 08:07 UTC

    Just check if the A-B exists before adding B-A.

    my %hash2 =( '1' => 2, '3' => 5, '4' => 2, ); my %spair = (); for my $si (keys %hash2) { for my $sn (keys %hash2) { next if exists $spair{$sn."-".$si}; $spair{$si."-".$sn} = ($si == $sn)? $hash2{$si} : $hash2{$si}* +$hash2{$sn}; } } use Data::Dumper; print Dumper \%spair; __DATA__ $VAR1 = { '3-3' => 5, '1-1' => 2, '3-4' => '10', '1-3' => '10', '1-4' => '4', '4-4' => 2 };

    cheers

    tachyon

Re: Self-Looping over hash - how to remove duplicate
by gothic_mallard (Pilgrim) on Oct 05, 2004 at 08:16 UTC

    The problem is that, although keys like 1-4 and 4-1 technically mean the same thing in context, as far as Perl is concerned they're different strings, so they're different keys. What you need to do is ensure that your keys are generated consistently.

    Your current code doesn't actually work as written as you've cut a few bits out (like the declaration of %hash2), but try this:

    Replace the line:
    $spair{$si."-".$sn} = $prod;

    With:

    if ( $si >= $sn ) { $spair{$si."-".$sn} = $prod; } else { $spair{$sn."-".$si} = $prod; }

    That ensures that the keys are always created in the same order (i.e. highest value first in this case) - which means that your keys should then map as you're expecting. Running the following complete code should give you want you want:

    use strict; use warnings; use Data::Dumper; my %hash2 =( '1' => 2, '3' => 5, '4' => 2,); my %spair = (); my $prod; foreach my $si (keys %hash2) { foreach my $sn (keys %hash2) { if ($si == $sn) { $prod = $hash2{$si}; } else { $prod = $hash2{$si}*$hash2{$sn}; } if ( $si >= $sn ) { $spair{$si."-".$sn} = $prod; } else { $spair{$sn."-".$si} = $prod; } } } print Dumper \%spair;

    Produces:

    $VAR1 = { '3-1' => '10', '3-3' => 5, '1-1' => 2, '4-1' => '4', '4-3' => '10', '4-4' => 2 };

    As always TMTOWTDI and there's almost certainly a more elegant solution but this albeit off-the-cuff should do the job.

    Hope this helps.

    --- Jay

    All code is untested unless otherwise stated.

Re: Self-Looping over hash - how to remove duplicate
by gaal (Parson) on Oct 05, 2004 at 08:07 UTC
    You could have both iterations go over the hash in an ordered manner, and then quit the inner loop after processing the key against itself. This is not tested:

    use strict; use warnings; #print Dumper \%hash2; my %spair = (); my $prod; foreach my $si (sort { $a <=> $b } keys %hash2) { INNER: foreach my $sn (sort { $a <=> $b } keys %hash2) { if ($si == $sn) { $prod = $hash2{$si}; $spair{$si."-".$sn} = $prod; #: $hash2{$si}\n"; } else { $prod = $hash2{$si}*$hash2{$sn}; $spair{$si."-".$sn} = $prod; #: $hash2{$si}\n"; last INNER; } } } print Dumper \%spair;
      Hi, thanks for the reply,
      but your code gives:
      $VAR1 = { '1-1' => 2, '3-1' => 10, '4-1' => 4, '1-3' => 10 };

      Notice that 2-2, 3-3, and 4-4 is not included
Re: Self-Looping over hash - how to remove duplicate
by atcroft (Abbot) on Oct 05, 2004 at 08:08 UTC

    Probably the easiest way to do this would be to change the following line:

    $spair{$si."-".$sn} = $prod;
    to something along the lines of:
    if ((! defined($spair{$si."-".$sn})) and (! defined($spair{$sn."-".$si}))) { $spair{$si."-".$sn} = $prod; }

    This will only add the entry for $si."-".$sn if there is no entry currently in %spair for either $si."-".$sn nor $sn."-".$si.

    Hope that helps.

Re: Self-Looping over hash - how to remove duplicate
by Hena (Friar) on Oct 05, 2004 at 08:14 UTC
    If you accept that you get always same order (eg, not 3-1, instead 1-3) then you can print smaller as first index and larger as second.
    if ($si == $sn) { $prod = $hash2{$si}; } else { $prod = $hash2{$si}*$hash2{$sn}; } my $tmp = $si; $si = $si <= $sn ? $si : $sn; $sn = $si == $sn ? $tmp : $sn; $spair{"$si-$sn"} = $prod;
      Easier way to swap two values:
      ($a,$b)=($b,$a);
      So we get:
      ($si,$sn) = sort {$a<=>$b} $si, $sn
      I believe this is straight out of 'Learning Perl'
Re: Self-Looping over hash - how to remove duplicate
by Anonymous Monk on Oct 05, 2004 at 12:25 UTC
    my %hash1; my %hash2 = (1 => 2, 3 => 5, 4 => 2,); my @keys = keys %hash2; for (my $i=0; $i<@keys; $i++) { $hash1{"$keys[$i]-$keys[$i]"}=$hash2{$keys[$i]}; for (my $j=$i+1; $j<@keys; $j++) { $hash1{"$keys[$i]-$keys[$j]"} = $hash2{$keys[$i]}*$hash2{$keys +[$j]}; } }