How to Sort Not Simple Hash (Hash of Hashes)

How to sort not simple hash (hash of hashes)

I would change the data structure to an array of hashes:

my_array =
[
{:id => 78, :value=>64, :rating=>-155},
{:id => 84, :value=>90, :rating=>-220},
{:id => 95, :value=>39, :rating=>-92}
]

You can sort this kind of structure easily with

my_array.sort_by { |record| record[:rating] }

To get the hash-like functionality of fetching a record by id you can define a new method on my_array:

def my_array.find_by_id(id) 
self.find { |hash| hash[:id] == id }
end

so after that you can do

my_array.find_by_id(id)

instead of

my_hash[id]

Sorting Hash of Hashes by value (and return the hash, not an array)

In Ruby 1.9, Hashes are sorted, but Hash#sort still returns an Array of Arrays. Imagine that! It does imply that you can build your own sorting method on top of it.

class Hash
def sorted_hash(&block)
self.class[sort(&block)] # Hash[ [[key1, value1], [key2, value2]] ]
end
end

Hashes are unsorted in Ruby 1.8. If you want Ruby 1.8 compatibility, you can use ActiveSupport's OrderedHash. It behaves like a 1.9-Hash, so you can define the same sorted_hash method on it:

class ActiveSupport::OrderedHash
def sorted_hash(&block)
self.class[sort(&block)]
end
end

hash = ActiveSupport::OrderedHash.new
hash["b"] = "b"
hash["a"] = "a"
hash #=> {"b"=>"b", "a"=>"a"} => unsorted
hash.sorted_hash #=> {"a"=>"a", "b"=>"b"} => sorted!

You have to copy the sorted_hash method to your code, because it does not exist by default!

Update for deep sorting:
If you're looking to sort on something else than the hash key, pass a block to the sorted_hash method as follows (assuming the implementation from above):

hash = ActiveSupport::OrderedHash.new
hash["a"] = { "attr" => "2", "..." => "..." }
hash["b"] = { "attr" => "1", "..." => "..." }

# Unsorted.
hash
#=> {"a"=>{"attr"=>"2", "..."=>"..."}, "b"=>{"attr"=>"1", "..."=>"..."}}

# Sort on the "attr" key. (Assuming every value is a Hash itself!)
hash.sorted_hash { |a, b| a[1]["attr"] <=> b[1]["attr"] }
#=> {"b"=>{"attr"=>"1", "..."=>"..."}, "a"=>{"attr"=>"2", "..."=>"..."}}

Sorting Hash of Hashes by value

  • %HoH is declared as a hash, but is defined as a hashreference. Use parentheses (...) instead of braces {...}.
  • You don't need to loop through the hash to sort it. Sort will take care of that.
  • if you sort {...} keys %HoH, then the special variables $a and $b represent the keys of %HoH as it performs the sort.
  • $a and $b are in reverse order because your expected result is in decreasing order. (Update: Oh I just noticed that you had that in the first place.)
  • The zip value in the nested hash is $HoH{$KEY}{'zip'}, which is what you should sort by.

    use strict;
    use warnings;
    use Data::Dumper;

    my %HoH = (
    'foo1' => {
    'bam' => 1,
    'zip' => 0,
    },
    'foo2' => {
    'bam' => 0,
    'zip' => 1,
    'boo' => 1
    }
    );

    my @sorted = sort {$HoH{$b}{'zip'} <=> $HoH{$a}{'zip'}} keys %HoH;
    print Dumper \@sorted;

Note that the result of this code will give you an array:

$VAR1 = [
'foo2',
'foo1'
];

... not a nested array:

$VAR1 = [
['foo2', 'foo1']
];

Sorting a hash of hashes by the inner hash's values

I think it's very unlikely that you need the data in a hash structure like that. Certainly for the purposes of this task you would be better off with an array of arrays

use strict;
use warnings;

my @change;

while ( <DATA> ) {
push @change, [ split ];
}

print "@$_\n" for sort { $b->[2] <=> $a->[2] } @change;

__DATA__
gene1 condition1 10
gene2 condition1 0.5
gene3 condition1 1.5
gene1 condition2 2
gene2 condition2 13.5
gene3 condition2 0.25

output

gene2 condition2 13.5
gene1 condition1 10
gene1 condition2 2
gene3 condition1 1.5
gene2 condition1 0.5
gene3 condition2 0.25

If you explain what sort of access you need to the data then I am sure there is something better. For instance, I would suggest %gene and %condition hashes that mapped a gene or condition ID to a list of the array elements that used that gene. Then you could access the data when you know either the gene or the condition

How can I sort an array of hashes by the hashes name?

Try

@sorted = sort {  (keys %$a)[0] cmp (keys %$b)[0] } @{$structure[$endpoint][1]};

This sorts the elements of the array (which are hash references) according to the first (only) key of each hash. If the keys are numeric use <=> instead.

Test code:

%a = ( 'a' => 1 );
%b = ( 'zz' => 2 );
%c = ( 'g' => 3);
@arr = (\%a, \%b, \%c);

print "Unsorted\n";
for (@arr)
{
printf "%s\n",((keys %$_)[0]);
}

@sorted = sort { (keys %$a)[0] cmp (keys %$b)[0] } @arr;

print "\nSorted\n";
for (@sorted)
{
printf "%s\n",((keys %$_)[0]);
}

How can I sort a hash of hashes by key in Perl?

my @hash1s = sort {$a->{count} <=> $b->{count}} values %hash2;

Sorting a Hash of Array of Hashes by Internal Hash Value

Assuming $VAR above is Data::Dumper::Dumper(\%matches) ...

If you don't want to make your data-structure nicer....

for my $gene ( sort keys %matches ) {
for my $hash ( sort {
my ($akey) = keys %$a;
my ($bkey) = keys %$b;
$a->{$akey} <=> $b->{$bkey}
} @{$matches{$gene}} ) {
my ($key) = keys %$hash;
print "$key => $hash->{$key}\n";
}
}

That sorts by the value (e.g: 12) not the key (e.g: 'p.I843_D846del'). I figured since you used a numeric comparison that you'd want to sort by the numeric value ;-)

edited: fixed body of inner loop.

edit 2:

I see you tried a Schwartzian Transform... if you keep your data-structure as is, that might be a more efficient solution... as follows:

for my $gene ( sort keys %matches ) {
print "$_->[0] => $_->[1]\n" for # print both key and value
sort { $a->[1] <=> $b->[1] } # sort by value (e.g: 35)
map { my ($key) = keys %$_; [$key, $_->{$key}] } # e.g:['p.D842_H845del' ,35]
@{$matches{$gene}};
}

But instead, I'd just fix the data structure.

Probably just make both the 'key' (e.g: 'p.I843_D846del') and the 'value' (e.g: 12) both values and give them consistent key names.

Is it possible to sort multidimensional hashes by child hash values?

You can convert it to an array of pairs, use sort_by method to target the value you want to sort by, and then convert it back to a hash:

h = {
:a =>
{:order => 3},
:b =>
{:order => 1},
:c =>
{:order => 2}
}

h.sort_by {|k,v| v[:order]}.to_h
=> {:b=>{:order=>1}, :c=>{:order=>2}, :a=>{:order=>3}}

Sort both levels of keys for hash of hashes in perl

Perl makes it easy to efficiently sort the keys while iterating through a hash of hashes:

for my $cat (sort keys %HoH) {
# numerical sort:
for my $digits (sort { $a <=> $b } keys %{$HoH{$cat}}) {
print join("\t", $cat, $digits, $HoH{$cat}{$digits}) . "\n";
}
}

Ruby how to sort hash of hashes?

Ok, you didn't specify your question, so I'm assuming you want one layer removed. I changed the starting hash a bit to actually see if the sorting works:

my_hash = { 
:category_1 => {
:solution_1 => { :order => 2 },
:solution_2 => { :order => 3 }
},
:category_2 => {
:solution_3 => { :order => 4 },
:solution_4 => { :order => 1 }
}
}

Hash[my_hash.inject({}) { |h, (k, v)| h.merge(v) }.sort_by { |k,v| v[:order] }]
#=> {:solution_4=>{:order=>1}, :solution_1=>{:order=>2}, :solution_2=>{:order=>3}, :solution_3=>{:order=>4}}

EDIT:

Taking into account your clarification (and still starting from the modified unsorted hash I posted above):

sorted = my_hash.inject({}) do |h, (k, v)| 
h[k] = Hash[v.sort_by { |k1, v1| v1[:order] }]
h
end
#=> {:category_1=>{:solution_1=>{:order=>2}, :solution_2=>{:order=>3}}, :category_2=>{:solution_4=>{:order=>1}, :solution_3=>{:order=>4}}}


Related Topics



Leave a reply



Submit