Kero
Kero

Reputation: 1944

sort the keys of deep nested hash according to value?

try to sort nested hash according to its values, long story,i parse %senseHash and store certain values in %rankedTest hash later, i want to loop through sort %rankedTest and output to file my code:

foreach my $str (keys %senseHash)
{
    my $selectedSense;
    if (exists $senseHash{$str}{'phone'} && exists $senseHash{$str}{'product'})
     {
        if ($senseHash{$str}{'phone'} > $senseHash{$str}{'product'})
        {
            $selectedSense='phone';
        }else
        {
            $selectedSense='product';
        }
         my $pPhone = $senseHash{$str}{'phone'} / $senses{'phone'};
         my $pProduct = $senseHash{$str}{'product'} / $senses{'product'};
         my $rankScore = abs(log($pPhone/$pProduct));
         $rankedTest{$str}{$selectedSense}=$rankScore;
     }
}
open($f1, '>', $inputs[1]);
foreach my $e (keys  %rankedTest)
{
    foreach my $s (sort { $rankedTest{$e}{$b} <=> $rankedTest{$e}{$a} } keys %{$rankedTest{$e}})
    {
        print $f2 "feature:$e -> sense:$s -> score:$rankedTest{$e}{$s} \n";
    }
} 

but output is something as below :

feature:operations -> sense:product -> score:0.0859904478555225 
feature:move -> sense:product -> score:0.309133999169732 
feature:dispute -> sense:phone -> score:0.0963311089384321 

while what i actually want is :

feature:move -> sense:product -> score:0.309133999169732 
feature:dispute -> sense:phone -> score:0.0963311089384321 
feature:operations -> sense:product -> score:0.0859904478555225

any clue what i did wrong or how i can accomplish this. any suggestion will help.

EDIT: inside hash has one key which could be phone or product and i want to sort according to what this key value regardless of the key whether phone or product

Upvotes: 0

Views: 317

Answers (2)

zdim
zdim

Reputation: 66883

To sort a multi-level hash by values in its bottom layer we have to compare all entries.

This code builds strings to be printed for all levels using (double) map, then sorts that

use warnings;
use strict;
use feature 'say';

# Example hash     
my %h = ( 
    oper => { prod => 0.8, phone => 0.03 },  
    disp => { prod => 0.2, phone => 0.05 },  
    move => { prod => 0.6, phone => 0.01 } 
); 

my @res = 
    sort { (split ":", $b)[-1] <=> (split ":", $a)[-1] }
    map { 
        my $k = $_; 
        map { "feature:$k -> sense:$_ -> score:$h{$k}{$_}" }
        keys %{$h{$k}} 
    }   
    keys %h; 

say for @res;

Output

feature:oper -> sense:prod -> score:0.8
feature:move -> sense:prod -> score:0.6
feature:disp -> sense:prod -> score:0.2
feature:disp -> sense:phone -> score:0.05
feature:oper -> sense:phone -> score:0.03
feature:move -> sense:phone -> score:0.01

I set phone values to be less than prod's to mimic posted data (irrelevant for processing).

The example hash %h stands for %rankedTest in the question. It may have either or both (or none) of prod and phone in subhashes.

Upvotes: 1

Borodin
Borodin

Reputation: 126722

Essentially, you're sorting the elements of the wrong hash. Your data structure %rankedTest looks like this

my %rankedTest = (
    operations => { product => 0.0859904478555225 },
    move       => { product => 0.309133999169732 },
    dispute    => { product => 0.0963311089384321 },
);

and you're sorting the keys of the inner hashes that look like { product => 0.0859904478555225 }. But there's only a single element in each of them so your inner for loop executes only once and sorting makes no difference

Here I've sorted the outer hash by the value of the product element of the inner hash

Note that it's often simpler to save intermediate results into temporary variables when dealing with complex data structures. Here I've stored the sorted keys in array @features and the value of the score in $score to save putting the long hash expression into the print statement

my @features = sort {
    $rankedTest{$b}{product} <=> $rankedTest{$a}{product}
} keys %rankedTest;

for my $feature ( @features ) {

    my $score = $rankedTest{$feature}{product};

    print "feature:$feature -> sense:product -> score:$score\n";
}

output

feature:move -> sense:product -> score:0.309133999169732
feature:dispute -> sense:product -> score:0.0963311089384321
feature:operations -> sense:product -> score:0.0859904478555225

Upvotes: 2

Related Questions