2015-10-24 3 views
0

В Ruby есть короткий и сладкий способ сортировки этот хэш массивов по баллам по убыванию:Сортировка хэш-массивов на одном из массивов в рубин

scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]} 

так это выглядит ?:

scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]} 

Я не смог найти пример, где я могу сортировать массивы внутри хэша, как это? Я мог бы сделать это с помощью какого-то неприятного кода, но я чувствую, что должен быть 1 или 2 лайнер, который это делает?

ответ

3

Вы можете использовать sort_by

scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]} 

scored.tap do |s| 
    s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] } 
    s[:score] = s[:score].sort_by{ |a| -a } 
end 
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]} 
+0

Ходила с этим, как я чувствовал, что это было немного более общительны. Спасибо за это. – joshweir

+0

Согласитесь с удобочитаемостью. Накладные расходы на выполнение двух вызовов sort_by будут незначительными во всех случаях, а также нет необходимости выделять дополнительные массивы, как в моем ответе. Единственная (высокотеоретическая) проблема параллелизма заключается в том, что если вы мутируете один балл между сортированными вызовами (что, конечно же, никогда не будет реальной жизнью), вы ввернуты. :-) Это, наверное, лучший ответ. – Drenmi

+0

@ Дренми, я не согласен. Мы не должны сортировать дважды, когда мы можем сортировать один раз. Если массивы значений были достаточно большими, методы, сортирующие один раз, были бы заметно более эффективными. Более того, сортировка дважды, imo, отрицательно влияет на читаемость. Извините, fl00r, но я не считаю, что этот ответ соответствует вашим обычным высоким стандартам. –

1

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

s = scored.values.inject(&:zip).sort_by(&:last).reverse 
#=> [[3, 10], [1, 8.3], [2, 5]] 

result = { id: s.map(&:first), score: s.map(&:last) } 
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] } 
2
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse 
    #=> [2,0,1] 
scored.update(scored) { |_,a| a.values_at *order } 
    #=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]} 

Если scored это не быть мутированным, заменить update на merge.

Некоторые пункты:

  • Computing order делает его легким для читателя, чтобы понять, что происходит.
  • Вторая строка использует форму Hash#merge, которая использует блок для определения значений ключей, которые присутствуют в обоих хешах, которые сливаются (что здесь все ключи). Это удобный способ изменения значений хэша (обычно), отчасти потому, что возвращается новый хеш.
  • Затем я отсортировал, а не отсортировал по отрицательным значениям, чтобы сделать метод более густым. (То есть, элементы массивов, которые являются значениями, могут быть из любого класса, который реализует <=>).
  • С Ruby 2.2+ другой способ сортировки массива arr в порядке убывания - использовать Enumerable#max_by: arr.max_by(arr.size).to_a.

Первая строка может быть заменена:

arr = scored[:score] 
order = arr.each_index.sort_by { |i| arr[i] }.reverse 
    #=> [2,0,1] 
+0

Мне это нравится. Особенно трюк с порядком. – fl00r

Смежные вопросы