Как найти максимальное значение, сгруппированное по нескольким ключам в массиве хэшей?

Имейте данные, которые имеют такую ​​структуру. Будет в порядке возрастания на «с».

[ { 'a' => 1, 'b' => 1, 'c' =>  1, 'd' => '?' },
  { 'a' => 1, 'b' => 1, 'c' =>  2, 'd' => '?' },
  { 'a' => 1, 'b' => 1, 'c' =>  3, 'd' => '?' },
  { 'a' => 1, 'b' => 2, 'c' =>  4, 'd' => '?' },
  { 'a' => 1, 'b' => 2, 'c' =>  5, 'd' => '?' },
  { 'a' => 2, 'b' => 1, 'c' =>  6, 'd' => '?' },
  { 'a' => 2, 'b' => 1, 'c' =>  7, 'd' => '?' },
  { 'a' => 2, 'b' => 1, 'c' =>  8, 'd' => '?' },
  { 'a' => 2, 'b' => 2, 'c' =>  9, 'd' => '?' },
  { 'a' => 2, 'b' => 2, 'c' => 10, 'd' => '?' } ]

Требуется массив максимального значения «c», сгруппированный по каждой уникальной комбинации «a» и «b».

[ { 'a' => 1, 'b' => 1, 'c' =>  3, 'd' => '?' },
  { 'a' => 1, 'b' => 2, 'c' =>  5, 'd' => '?' },
  { 'a' => 2, 'b' => 1, 'c' =>  8, 'd' => '?' },
  { 'a' => 2, 'b' => 2, 'c' => 10, 'd' => '?' } ]

Остальные ключи необходимо сохранить, но они никак не связаны с трансформацией. Лучшее, что я мог понять до сих пор, - это перевернуть массив (таким образом, по убыванию, упорядоченный по «c»), uniq по «a» и «b» и снова перевернуть массив. Но я в зависимости от реализации uniq_by всегда возвращаю первый найденный уникальный элемент. В спецификации об этом не говорится, поэтому я беспокоюсь о том, чтобы полагаться на это поведение, поскольку оно может измениться в будущих версиях. Также интересно, может ли это быть действительно неэффективным методом.

@data.reverse!.uniq!{|record| [record['a'],record['b']]}.reverse!

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


person Douglas Mauch    schedule 16.05.2012    source источник


Ответы (1)


Это на самом деле довольно просто:

a.group_by { |h| h.values_at("a", "b") }.map { |_, v| v.max_by { |h| h["c"] } } 

Или с более приятным форматированием:

a.group_by do |h|
  h.values_at("a", "b") 
end.map do |_, v| 
  v.max_by { |h| h["c"] }
end

Объяснение: сначала мы используем Enumerable#group_by для создания Hash с комбинации "a" и "b" (извлекаются с помощью Hash#values_at) в качестве ключей и всех хэшей с этой комбинацией в качестве значений. Затем мы сопоставляем этот хеш, игнорируем ключи и выбираем элемент с максимальным значением для "c" из массива с помощью Перечислить#max_by.

person Michael Kohl    schedule 16.05.2012
comment
Не могли бы вы объяснить или дать ссылку на значение _ в параметрах блока? - person Flexoid; 16.05.2012
comment
@Flexoid: нет особого значения, это параметр, который меня не волнует, и во многих языках принято использовать подчеркивание для имени, чтобы обозначить это. - person Michael Kohl; 16.05.2012
comment
@steenslag Каким-то образом "c" превратилось в v, и мне потребовалась секунда, чтобы понять, где я был тупым ;-) Перечитывание моего текстового описания помогло, потому что я описал его правильно... - person Michael Kohl; 16.05.2012
comment
+1 за очень идиоматическое решение, особенно с использованием values_at (что я бы не смог сделать). Хотя я предпочел .last @steenslag вместо _,v desplat. Или еще лучше, a.group_by{...}.values.map{...} - person Phrogz; 16.05.2012
comment
_ имеет особое значение или, по крайней мере, в некоторых случаях получает особое обращение, соглашение об использовании _ в качестве Мне все равно параметр имеет встроенную поддержку в интерпретаторе. Это просто гнида, выбирающая комментарий, хотя :) - person mu is too short; 16.05.2012