Получить верхние n элементов из массива рубинов хэш-значений

Привет, у меня есть массив, в котором каждый элемент является хешем, содержащим несколько значений и счет.

result = [
           {"count" => 3,"name" => "user1"}, 
           {"count" => 10,"name" => "user2"}, 
           {"count" => 10, "user3"},
           {"count" => 2, "user4"}
         ]

Я могу сортировать массив по счету следующим образом:

result = result.sort_by do |r|
  r["count"]
end

Теперь я хочу иметь возможность извлекать верхние n записей на основе count (а не только сначала (n)). Есть ли элегантный способ сделать это? Итак, в качестве примера, пусть n = 1, я ожидал бы набор результатов.

[{"count" => 10,"name" => "user2"}, {"count" => 10, "user3"}]

так как я попросил все записи с наивысшим результатом. Если бы я попросил высшие 2 наивысших оценки, я бы получил

 [{"count" => 10,"name" => "user2"}, {"count" => 10, "user3"}, {"count" => 3, "user1"}]

Ответ 1

Enumerable#group_by для спасения (как обычно):

result.group_by { |r| r["count"] }
      .sort_by  { |k, v| -k }
      .first(2)
      .map(&:last)
      .flatten

Большая часть работы выполняется с помощью group_by. sort_by просто строит вещи так, чтобы first(2) выберет группы, которые вы хотите. Затем map с last выберет счетчик /name хэши, с которых вы начали, и окончательный flatten очистит лишние оставшиеся массивы.

Ответ 2

Это решение не является элегантным с точки зрения краткости, но оно имеет более сложную временную сложность. Другими словами, он должен выполнять намного быстрее для очень большого количества хэшей.

Вам понадобится установить "алгоритмы" для использования структуры данных кучи:

Heaps - эффективная структура данных, когда вам нужно найти самые большие или самые маленькие элементы в группе. Этот тип кучи оптимальный, если значение "n" намного меньше, чем общее количество пар.

require 'algorithms'
def take_highest(result,n)
  max_heap = Containers::Heap.new(result){|x,y| (x["count"] <=> y["count"]) == 1}
  last = max_heap.pop
  count = 0
  highest = [last]
  loop do   
    top = max_heap.pop
    break if top.nil?
    count += (top["count"] == last["count"] ? 0 : 1)
    break if count == n
    highest << top
    last = top
  end
  highest
end

Ответ 3

new_result = result.
  sort_by { |r| -r["count"] }.
  chunk { |r| r["count"] }.
  take(2).
  flat_map(&:last)

#=> [{"count"=>10, "name"=>"user3"}, 
#    {"count"=>10, "name"=>"user2"}, 
#    {"count"=> 3  "name"=>"user1"}]

Ответ 4

Начиная с Ruby 2.2.0, max_by принимает дополнительный аргумент, который позволяет вам запрашивать определенное количество верхних элементов, а не просто получая один. Используя это, мы можем улучшить на mu слишком короткий ответ

result = [
           {count: 3, name: 'user1'},
           {count: 10, name: 'user2'},
           {count: 10, name: 'user3'},
           {count: 2, name: 'user4'}
         ]
p result.group_by { |r| r[:count] }
      .max_by(2, &:first)
      .flat_map(&:last)
      .sort_by { |r| -r[:count] }
# => [{:count=>10, :name=>"user2"}, {:count=>10, :name=>"user3"}, {:count=>3, :name=>"user1"}]

Документы не говорят, отсортирован ли массив, возвращаемый max_by. Если это правда, хотя мы могли бы просто использовать reverse на последнем шаге вместо сортировки.