Dan Rubio
Dan Rubio

Reputation: 4897

How can I sort this hash with an array as a value?

I have the following hash

{"f"=>[0, 1], "i"=>[1, 2], "n"=>[2, 2], "d"=>[3, 1], "g"=>[6, 1]}

I ultimately want to grab this value out of it "i"=>[1, 2]. I want to grab this value because the logic is that out of all these characters in some string, i has the highest value of occurrences 2 but appears first in a string given by its index in the array 1.

So for a string 'finding' the i character would be the first returned. I've made it so I generated this hash, now I just need to sort it so that the character that has the lowest index but the highest count will be first. Does anyone have an elegant solution to this?

Upvotes: 1

Views: 57

Answers (2)

lurker
lurker

Reputation: 58244

In the Ruby sort_by block, Ruby can compare arrays, which is done in element order. In your case, you want reverse order by the second element of the array, then order by first element. So you can construct your sort block as follows:

arr = {"f"=>[0, 1], "i"=>[1, 2], "n"=>[2, 2], "d"=>[3, 1], "g"=>[6, 1]}

arr.sort_by { |a| [-a[1][1], a[1][0]] }.first

Ruby firsts converts arr to an array that looks like this:

[["f", [0, 1]], ["i", [1, 2]], ["n", [2, 2]], ["d", [3, 1]], ["g", [6, 1]]]

Then for each element that looks like [letter, [position, count]] (represented by the sort block argument, a), it is comparing [-count, position] for the sort.

This will give:

["i", [1,2]]

You can then do with that form whatever you wish.


Note, you can use max_by ... instead of sort_by ... .first in the above. I completely forgot about max_by, but Jörg W Mittag's nice answer reminded me.

Upvotes: 1

Jörg W Mittag
Jörg W Mittag

Reputation: 369458

If I understand the question correctly, there is no need to sort the hash at all to get the answer you are looking for, since you never actually need the sorted hash, you only need the maximum.

So, something like this should do the trick:

hash.max_by {|(_, (idx, frequency))| [frequency, -idx] }
#=> [?i, [1, 2]]

The entire thing would then look something like this:

str = 'Hello Nett'

str.
  each_char.
  with_index.
  each_with_object(Hash.new {|h, k| h[k] = [0, nil]}) do |(char, idx), acc|
      next unless /\p{Alphabetic}/ === char
      char = char.downcase
      acc[char][1] ||= idx
      acc[char][0] += 1
    end.
max_by {|(_, (frequency, idx))| [frequency, -idx] }
#=> [?n, [2, 1]]

Upvotes: 1

Related Questions