Luke Luke - 25 days ago 11
Perl Question

Perl sort hash of hashes by the third-level key and compare it

My data structure is

my %hash = (
firstkey => {
secondkey => {
2 => ['9','2'],
1 => ['3','4'],
3 => ['8','2']
}
}
);

print Dumper \%hash;


I want to sort the hash by the thirdly key. i.e.
1
,
2
and
3
in this case
and then compare the second element (
index[1]
) in the array. If they are the same, and then print it out.

Expected Sorted Hash:

my %hash = (
firstkey => {
secondkey => {
1 => ['3','4'],
2 => ['9','2'],
3 => ['8','2']
}
}
);

print Dumper \%hash;


After sort the hash, we compare the
index[1]
of the 1st array
[3,4]
with the 2nd array
[9,2]
.
4
is not equal to
2
, so we are not going to print anything.

Then, we compare the
index[1]
of the 2nd array
[9,2]
with the 3rd array
[4,2]
.
2
is equal to
2
, then we are going to print all the content of it

firstkey, secondkey, 3, [8,2]


we only need to compare the adjacent array.

I read a lot of solutions about sorting the hash, but I couldn't find one solution that really reorders it Is it any way to reorder the hash by the key and construct a hash with the new order in Perl?
Or we can only sort the hash by using the for loop and compare it in the for loop?

Answer Source

One cannot have a "sorted hash" – they are intrinsically unordered data structures (see keys). The randomizaton of the initial seed and hash traversal are even enhanced for security purposes.

But we can sort the list of hash keys as needed. Then we have an ordered list to iterate over and thus can process the hash in a "sorted manner."

The keys to sort by here are at a deeper level, so iterate over the upper (two) levels to get to them. Then it's a straightforward sort and test

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

my %hash = ( 
    firstkey1 => { 
        secondkey1 => { 
            2 => [9, 2], 1 => [3, 4], 3 => [8, 2]  
        }   
    }   
);

foreach my $k1 (keys %hash) 
{
    foreach my $k2 (keys %{$hash{$k1}}) 
    {   
        # Relieve syntax below
        my $hr = $hash{$k1}{$k2};

        my @sr_k3 = sort { $a <=> $b } keys %{$hr};

        foreach my $i (1..$#sr_k3)
        {
            if ( $hr->{$sr_k3[$i]}[1] == $hr->{$sr_k3[$i-1]}[1] )
            {
                say "$k1, $k2, $sr_k3[$i], ", 
                    '[', join(',', @{$hr->{$sr_k3[$i]}}), ']';
            }
        }   
        #say "@{$hash{$k1}{$k2}{$_}}" for keys %{$hash{$k1}{$k2}};
    }   
}

A few notes

  • Sorted keys are iterated over starting with the second one due to the comparison criterion

  • Hashref is copied at the second level only for convenience, to relieve the messy syntax

  • When complex data structures get too unwieldy it may be time to use a class instead

This works for any number of keys in both levels (only one key is shown for each level).