user2398029
user2398029

Reputation: 6937

Finding N keys with highest value in hash, keeping order

In a Ruby script,

Given:

hash = {
  'This is the first sentence.' => 5,
  'This is the second sentence.' => 1,
  'This is the last sentence.' => 6
}

Then:

choose_best(hash, 2)

Should return:

['This is the first sentence.', 'This is the last sentence.']

All the methods I can think of involve reordering the hash, thus losing the order of the sentences. What would be the best way to tackle this?

Upvotes: 2

Views: 1302

Answers (5)

David Grayson
David Grayson

Reputation: 87386

Starting in Ruby 2.2.0, Enumerable#max_by takes an optional integer argument that makes it return an array instead of just a single element. Therefore, we can do:

hash = {
  'This is the first sentence.' => 6,
  'This is the second sentence.' => 1,
  'This is the last sentence.' => 5
 }

p hash.max_by(2, &:last).map(&:first).sort_by { |k| hash.keys.index k }
# => ["This is the first sentence.", "This is the last sentence."]

The call to sort_by at the end guarantees the sentences are in the right order, as you requested.

Upvotes: 0

sawa
sawa

Reputation: 168091

def extract hash, n
  min = hash.values.sort[-n]
  a = []
  i = 0
  hash.each{|k, v| (a.push(k) and i += 1) if i < n and v >= min}
  a
end

Upvotes: 2

steenslag
steenslag

Reputation: 80065

hash = {
  'This is the first sentence.' => 5,
  'This is the second sentence.' => 1,
  'This is the last sentence.' => 6
}

cutoff_val = hash.values.sort[-2] #cf. sawa
p hash.select{|k,v| v >= cutoff_val } 
# =>{"This is the first sentence."=>5, "This is the last sentence."=>6}

Upvotes: 1

Niklas B.
Niklas B.

Reputation: 95298

Try the following monster:

hash.map(&:reverse).each_with_index
                   .sort_by(&:first).reverse
                   .take(2)
                   .sort_by(&:last)
                   .map { |(_,s),_| s }

Another functional one:

hash.to_a.values_at(*hash.values.each_with_index
                         .sort.reverse
                         .map(&:last)
                         .sort.take(2))
         .map(&:first)

Note however, that as an unordered data structure, a hash table is not really suitable for this use case (although the order is remembered in Ruby 1.9). You should use an array instead (the sorting code remains the same):

sentences = [
  ['This is the first sentence.',  5],
  ['This is the second sentence.', 1],
  ['This is the last sentence.',   6],
]

Upvotes: 2

Marc Talbot
Marc Talbot

Reputation: 2059

a = hash.sort_by { |sentence, score| score }.reverse

The array a now contains pairs of values of your top scoring sentences. You can select the first N of them.

hash = {"foo" => 7, "bar" => 2, "blah" => 3 }
a = hash.sort_by { |sentence, score| score }.reverse
=> [["foo", 7], ["blah", 3], ["bar", 2]]

Upvotes: -1

Related Questions