Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort items in a nested hash by their values

Tags:

sorting

ruby

I'm being sent a nested hash that needs to be sorted by its values. For example:

@foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}

When running the following:

@foo["a"].sort{|a,b| a[1]<=>b[1]}

I get:

[["y", 3], ["z", 5], ["x", 88]]

This is great, it's exactly what I want. The problem is I'm not always going to know what all the keys are that are being sent to me so I need some sort of loop. I tried to do the following:

@foo.each do |e|   
  e.sort{|a,b| a[1]<=>b[1]}
end

This to me makes sense since if I manually call @foo.first[0] I get

"a"

and @foo.first[1] returns

{"z"=>5, "y"=>3, "x"=>8}

but for some reason this isn't sorting properly (e.g. at all). I assume this is because the each is calling sort on the entire hash object rather than on "a"'s values. How do I access the values of the nested hash without knowing what it's key is?

like image 781
keybored Avatar asked Feb 07 '11 17:02

keybored


3 Answers

You might want to loop over the hash like this:

@foo.each do |key, value|
  @foo[key] = value.sort{ |a,b| a[1]<=>b[1] }
end
like image 156
Pan Thomakos Avatar answered Sep 23 '22 20:09

Pan Thomakos


@foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}
@bar = Hash[ @foo.map{ |key,values| [ key, values.sort_by(&:last) ] } ]

Or, via a less-tricky path:

@bar = {}
@foo.each do |key,values|
  @bar[key] = values.sort_by{ |key,value| value }
end

In both cases @bar turns out to be:

p @bar
#=> {
#=>   "a"=>[["y", 3], ["z", 5], ["x", 88]],
#=>   "b"=>[["d", -5], ["a", 2]]
#=> }
like image 31
Phrogz Avatar answered Sep 23 '22 20:09

Phrogz


My coworker came up with a slightly more flexible solution that will recursively sort an array of any depth:

def deep_sort_by(&block)
  Hash[self.map do |key, value|
    [if key.respond_to? :deep_sort_by
      key.deep_sort_by(&block)
    else
      key
    end,

    if value.respond_to? :deep_sort_by
      value.deep_sort_by(&block)
    else
      value
    end]

  end.sort_by(&block)]
end

You can inject it into all hashes and then just call it like this:

myMap.deep_sort_by { |obj| obj }

The code would be similar for an array. We published it as a gem for others to use, see blog post for additional details.

Disclaimer: I work for this company.

like image 40
Dmitry Pashkevich Avatar answered Sep 25 '22 20:09

Dmitry Pashkevich