Reputation: 769
I believe this is how you would normally sort a hash by value:
foreach my $key (sort { $hash{$a} <=> $hash{$b} } (keys %hash) ) {
print "$key=>$hash{$key}";
}
This would print out the values smallest to largest.
Now, what if I have a hash like this:
$hash{$somekey}{$somekey2}{$thirdkey}
How could I sort by values and get all the keys as well?
Upvotes: 5
Views: 331
Reputation: 42411
Here's a way to do it using Deep::Hash::Utils.
use Deep::Hash::Utils qw(slurp);
my %h = (
A => {
Aa => { Aaa => 4, Aab => 5 },
Ab => { Aba => 1 },
Ac => { Aca => 2, Acb => 9, Acc => 0 },
},
B => {
Ba => { Baa => 44, Bab => -55 },
Bc => { Bca => 22, Bcb => 99, Bcc => 100 },
},
);
my @all_keys_and_vals = slurp \%h;
print "@$_\n" for sort { $a->[-1] <=> $b->[-1] } @all_keys_and_vals;
Output:
B Ba Bab -55
A Ac Acc 0
A Ab Aba 1
A Ac Aca 2
A Aa Aaa 4
A Aa Aab 5
A Ac Acb 9
B Bc Bca 22
B Ba Baa 44
B Bc Bcb 99
B Bc Bcc 100
Upvotes: 1
Reputation: 52029
I would just create a new hash:
my %new;
for my $k1 (keys %hash) {
for my $k2 (keys %{$hash{$k1}}) {
for my $k3 (keys %{$hash{$k1}{$k2}}) {
$new{$k1,$k2,$k3} = $hash{$k1}{$k2}{$k3};
}
}
}
my @ordered = sort { $new{$a} <=> $new{$b} } keys %new;
for my $k (@ordered) {
my @keys = split($;, $k);
print "key: @k - value: $new{$k}\n";
}
Upvotes: 3
Reputation: 239821
For academic purposes, here's a fairly tidy recursive function:
sub flatten_hash {
my ($hash, $path) = @_;
$path = [] unless defined $path;
my @ret;
while (my ($key, $value) = each %$hash) {
if (ref $value eq 'HASH') {
push @ret, flatten_hash($value, [ @$path, $key ]);
} else {
push @ret, [ [ @$path, $key ], $value ];
}
}
return @ret;
}
which takes a hash like
{
roman => {
i => 1,
ii => 2,
iii => 3,
},
english => {
one => 1,
two => 2,
three => 3,
},
}
and turns it into a list like
(
[ ['roman','i'], 1 ],
[ ['roman', 'ii'], 2 ],
[ ['roman', 'iii'], 3 ],
[ ['english', 'one'], 1 ],
[ ['english', 'two'], 2 ],
[ ['english', 'three'], 3 ]
)
although of course the order is bound to vary. Given that list, you can sort it on { $a->[1] <=> $b->[1] }
or similar, and then extract the key path from @{ $entry->[0] }
for each entry. It works regardless of the depth of the data structure, and even if the leaf nodes don't occur all at the same depth. It needs a little bit of extension to deal with structures that aren't purely of hashrefs and plain scalars, though.
Upvotes: 1
Reputation: 533
I have done something similar by moving a reference down to the appropriate hash key. You can then perform the sort on the pointer.
The advantage to doing it this way is that it is easy to adjust if the level changes.
What I have used this methodology for is systematically moving the pointer to a specific level by referencing an array of keys. (Ex: my @Keys = ('Value', 'Value2');)
I believe a derivative of the following example might give you what you are looking for.
my $list_ref;
my $pointer;
my %list = (
Value => {
Value2 => {
A => '1',
C => '3',
B => '2',
},
},
);
$list_ref = \%list;
$pointer = $list_ref->{Value}->{Value2};
foreach my $key (sort { $pointer->{$a} <=> $pointer->{$b} } (keys %{$pointer})) {
print "Key: $key\n";
}
Upvotes: 1