How to sort a hash by values
Asked Answered
P

4

2

I was trying to sort a particular hash by values. I came across a way using the method sort_by. But even though I call sort_by on a hash, it returns an array, i.e.:

a = {}
a[0] = "c"
a[1] = "b"
a[2] = "a"
a.sort_by{|key, value| value}
# => [[2, "a"], [1, "b"], [0, "c"]]

If I try to convert the result into a hash, I end up with a hash sorted on key, hence the whole purpose of sort_by is lost. i.e.:

a = {}
a[0] = "c"
a[1] = "b"
a[2] = "a"
Hash[*a.sort_by{|key, value| value}.flatten]
# => {0=>"c", 1=>"b", 2=>"a"}

Is there a way I can sort a hash by value and yet get the results back in the form of a Hash?

I am using 1.8.6 ruby

Portent answered 19/4, 2013 at 11:5 Comment(5)
duplicate #2455511Faithfaithful
@Faithfaithful I don't think this is a duplicate question. The question there pertains specifically to getting the values sorted. But here, I want back a result with a hash sorted "on" values.Portent
Why do you want to use a Hash?Ergocalciferol
@MarkThomas This hash is used at many places across views and helpers. The problem magnifies the hash is passed as parameters to other functions. Changing the datatype is not a solution as many Hash specific conditional constructs were introduced.Portent
Sorry to hear that refactoring to a better design would be difficult. However, I would say that it is not impossible. I would probably create a class that (initially) inherits from Hash. This way you can still use it as described in your existing code, but be able to add methods that give you output in the order you want, for example. With a well-named class modeling this part of your domain, you'll be able to tuck away a lot of your data structure manipulation code into the class and your code will be cleaner and the intent will be clearer.Ergocalciferol
W
2

You can use ActiveSupport::OrderedHash for Ruby 1.8:

ActiveSupport::OrderedHash implements a hash that preserves insertion order, as in Ruby 1.9

I don't have 1.8.6 running, but this should work:

a = {}
a[0] = "c"
a[1] = "b"
a[2] = "a"

ordered = ActiveSupport::OrderedHash[*a.sort_by{|k,v| v}.flatten]
ordered.keys
# => [2, 1, 0], this order is guaranteed

As noted in the quote above hashes in Ruby 1.9 "enumerate their values in the order that the corresponding keys were inserted", so this is only needed for Ruby 1.8.

Widely answered 19/4, 2013 at 11:52 Comment(2)
Yes, an orderedHash does retain the order in which the elements were created. But the problem lies in the fact that an orderedHash is again an array of arrays. I guess with lower versions of ruby, we have to compromise on the fact that Hash cannot be sorted on values and be retained as a Hash datatype. Also, the solution you have given will not work as sort_by will always return an array of arrays. Hence, creating a new OrderedHash will result in an array of array of arrays! ;)Portent
You're right, Hash::[] doesn't accept nested arrays in Ruby 1.8, I've updated my answer.Widely
L
1

A Hash is a collection of key-value pairs. It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index. The order in which you traverse a hash by either key or value may seem arbitrary, and will generally not be in the insertion order.

Source: Ruby 1.8.7 docs

  • Hash preserves order. It enumerates its elements in the

Source: Ruby 1.9.1 changelog

If you want to use a ordered hash in Ruby 1.8, you should look at ActiveSupport's OrderedHash

You don't need to be in a Rails project, just include ActiveSupport in your project.

Litho answered 19/4, 2013 at 11:32 Comment(2)
Thanks for the response. But I guess i've already mentioned that I want to retain the datatype. Hence, with the lower versions of ruby, we dont have the option. OrderedHash is a better option though! Cheers!Portent
The whole ActiveSupport doesn't have to be included, you can cherry-pick specific parts to require. See edgeguides.rubyonrails.org/active_support_core_extensions.htmlBreakthrough
P
0

It's just an another way to do @stefan's answer and I am using Ruby 2.6.3.

hash = { 0: 'c', 1: 'b', 2: 'a' }
hash.sort_by { |key, value| value  }.to_h
{2=>"a", 1=>"b", 0=>"c"} # You will get the array of key and value as result then you are converting the array of value to hash (Ruby 2.6.3).
Portland answered 24/5 at 5:32 Comment(0)
C
-1

Your description is wrong.

a = {}
a[0] = "c"
a[1] = "b"
a[2] = "a"
Hash[*a.sort_by{|key, value| value}.flatten]

gives:

{
  2 => "a",
  1 => "b",
  0 => "c"
}

which is sorted by value. By the way, your code is redundant. A better way to to this is:

Hash[a.sort_by{|_, value| value}]

or

Hash[a.sort_by(&:last)]

After Seeing Your Edit You are using a version that does not have ordered hash. You cannot control the order of the elements. You cannot do anything with it.

Crib answered 19/4, 2013 at 11:11 Comment(3)
I tried it again after you commented regarding the description. I did get the same hash back which is having data sorted on keys and not on values. Which version of ruby? Does ruby version also matter?Portent
It should work like this as long as the version is 1.9 or higher.Crib
Ok. I'm on 1.8.6. Edited my question for the same.Portent

© 2022 - 2024 — McMap. All rights reserved.