1
votes

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?

2
You cannot "sort hash" -- they are intrinsically unordered. But you can get a sorted list of keys, using sort on the list of hash's keys with any criteria you wish. Then you have an ordered list of keys to iterate though so you can use your hash in a "sorted manner." See this post for sorting a similar hash, or this post for a bit more involved sort with some comments. There is a lot more out there. - zdim
@zdim Thanks for your suggestions. I am trying that. - Luke
While the above comment is in general fine and needed, and worth following through, it may not help in this problem (and so could be misleading) -- I posted an answer. - zdim

2 Answers

3
votes

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).

0
votes

As has been said, you won't be able to dictate the order of the hash. Here's a way to map it to something you can sort and do the comparison you need.

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

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


#obtain an array of two-element array refs, whose contents are the keys
#for the "secondkey" hash and the corresponding value, respectively

my @arr = map { [ $_, $hash{firstkey}->{secondkey}->{$_} ] }
          keys %{$hash{firstkey}->{secondkey}};

#sort on the aforementioned key

my @sorted = sort { $a->[0] <=> $b->[0] } @arr;

#obtain an array of array refs, whose contents are a pair of adjacent 
#elements from the @sorted array

my @ordered_pairs = map { [ $sorted[$_], $sorted[$_+1] ] }
                    (0 .. (scalar @sorted - 2));

#compare the elements in question, and do something if there's a match

for (@ordered_pairs) {
    if ($_->[0][1][1] == $_->[1][1][1]) {
        print Dumper $_->[1];
    }
}