Sorting a Hash in Ruby by Its Value First Then Its Key

Sorting a hash in Ruby by its value first then its key

Try this:

Assuming:

a = { 
'the' => '6',
'we' => '7',
'those' => '5',
'have' => '3',
'hav' => '3',
'haven' => '3'
}

then after doing this:

b = a.sort_by { |x, y| [ -Integer(y), x ] }

b will look like this:

[
["we", "7"],
["the", "6"],
["those", "5"],
["hav", "3"],
["have", "3"],
["haven", "3"]
]

Edited to sort by reverse frequencies.

Wanting to sort a hash in ruby by value first, and key second

Array has a cool feature: comparing two arrays with the <=> method will compare them element-by-element. You can exploit this for multi-value sorting:

sorted_hash = Hash[original_hash.sort_by { |k,v| [-1 * v, -1 * k] }]

UPDATE: Maybe you know this already, but I assumed you're using Ruby 1.9. In earlier versions, Hashes are not guaranteed to maintain their order.

Sorting a hash in ruby based on value and then key

h.sort_by {|k, v| [v, k] }

This uses the fact that Array mixes in Comparable and defines <=> element-wise.

Note that the above is equivalent to

h.sort_by {|el| el.reverse }

which is equivalent to

h.sort_by(&:reverse)

which may or may not be more readable.

If you know that Hashes usually sort by key first, then by value, the above should be obvious. Maybe with a small comment:

h.sort_by(&:reverse) # sort by value first, then by key

Note: if you simply want to delegate to some property's <=> method, (i.e. sort by a key rather than a general comparison function) it is generally prefered to use sort_by instead of sort. It's much easier to read. Generally, it also happens to be faster, but the readability aspect is the more important one.

You should really only write your own comparison function if absolutely necessary.

So, your non-working example would better be written as

h.sort_by {|el| el[1] }

Personally, I prefer to use destructuring bind in the block parameter list, instead of using el[0] and el[1]:

h.sort_by {|key, value| value }

However, in this particular case, el[1] also happens to be identical to el.last, so that you could simply write

h.sort_by(&:last)

Sort an array of hashes by hash's first element with key as a string

Part of the difficulty is that your data structure doesn’t really make sense. Instead let’s make the “key” be simply an element of each hash (change :key to whatever semantically makes sense):

items = [...] # your data from question
items = items.map do |hash|
key, values = hash.first
{ key: key }.merge(values)
end
#=> [{:key=>"XYZ140717", :unit=>10.25, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"},
# {:key=>"ABC140719", :unit=>8.71, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"},
# {:key=>"MNO140718", :unit=>8.55, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"}]

Now it becomes rather simple to use sort_by:

items.sort_by { |item| item[:key] }
#=> [{:key=>"ABC140719", :unit=>8.71, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"},
# {:key=>"MNO140718", :unit=>8.55, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"},
# {:key=>"XYZ140717", :unit=>10.25, :perc=>0.0, :exp=>"2014-07-19 00:00:00 +1000"}]

If you insist on keeping your current data structure, you can of course do the following:

items.sort_by { |hash| hash.keys.first }

Sorting a hash by value then by key (but the key is reversed)

Use sort:

total = {"Bob"=>37, "Alice"=>42, "Carl"=>42}
total.sort { |(k1, v1), (k2, v2)| [v1, k2] <=> [v2, k1] }.to_h
# => {"Bob"=>37, "Carl"=>42, "Alice"=>42}

First, sort by values (v1 <=> v2) and then reverse sort by keys (k2 <=> k1), and since we need it simultaneously, put it into array.

EDIT: @Mirror318, it just looks scary, take a look at the excellent explanation here: What is the Ruby <=> (spaceship) operator?

How to sort a Ruby Hash by number value?

No idea how you got your results, since it would not sort by string value... You should reverse a1 and a2 in your example

Best way in any case (as per Mladen) is:

metrics = {"sitea.com" => 745, "siteb.com" => 9, "sitec.com" => 10 }
metrics.sort_by {|_key, value| value}
# ==> [["siteb.com", 9], ["sitec.com", 10], ["sitea.com", 745]]

If you need a hash as a result, you can use to_h (in Ruby 2.0+)

metrics.sort_by {|_key, value| value}.to_h
# ==> {"siteb.com" => 9, "sitec.com" => 10, "sitea.com", 745}

How to sort a Ruby Hash alphabetically by keys

Assuming you want the output to be a hash which will iterate through keys in sorted order, then you are nearly there. Hash#sort_by returns an Array of Arrays, and the inner arrays are all two elements.

Ruby's Hash has a constructor that can consume this output.

Try this:

temp = Hash[ temp.sort_by { |key, val| key } ]

or more concisely

temp = temp.sort_by { |key| key }.to_h

If your hash has mixed key types, this will not work (Ruby will not automatically sort between Strings and Symbols for instance) and you will get an error message like comparison of Symbol with String failed (ArgumentError). If so, you could alter the above to

temp = Hash[ temp.sort_by { |key, val| key.to_s } ] 

to work around the issue. However be warned that the keys will still retain their original types which could cause problems with assumptions in later code. Also, most built-in classes support a .to_s method, so you may get unwanted results from that (such as unexpected sort order for numeric keys, or other unexpected types).

You could, in addition, convert the keys to Strings with something like this:

temp = Hash[ temp.map { |key, val| [key.to_s, val] }.sort ] 

. . . although this approach would lose information about the type of the original key making it impossible to refer back to the original data reliably.

Is there a way of changing the key of a hash via sort / sort_by?

Looks like you're trying to split the hash into keys and values, sort each of those separately, and then put them back together as a hash.

In that case, you could do something like this:

hash.to_a.transpose.map(&:sort).transpose.to_h

Step by step it works like this:

# First array-ify the hash into key/value pairs
hash.to_a
# [[:a, 2], [:b, 5], [:c, 1]]

# Then transpose to group the keys and values together
hash.to_a.transpose
# [[:a, :b, :c], [2, 5, 1]]

# Then sort the keys and values separately
hash.to_a.transpose.map(&:sort)
# [[:a, :b, :c], [1, 2, 5]]

# And transpose again to get key/value pairs
hash.to_a.transpose.map(&:sort).transpose
# [[:a, 1], [:b, 2], [:c, 5]]

# And combine the array of key/value pairs into a hash
hash.to_a.transpose.map(&:sort).transpose.to_h
# {:a=>1, :b=>2, :c=>5}

You could also manually do the hash.to_a.transpose step like this:

[hash.keys, hash.values].map(&:sort).transpose.to_h

You don't even have to assume that #keys and #values will produce arrays in any particular order since you're sorting everything anyway.



Related Topics



Leave a reply



Submit