Как получить уникальные элементы из массива хэшей в Ruby? - PullRequest
24 голосов
/ 08 октября 2008

У меня есть массив хэшей, и я хочу получить из него уникальные значения. Звонок Array.uniq не дает мне того, чего я ожидаю.

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.uniq # => [{:a => 1}, {:a => 2}, {:a => 1}]

Где я ожидал:

[{:a => 1}, {:a => 2}]

При поиске в сети я не нашел решения, которое меня порадовало. Люди рекомендовали переопределить Hash.eql? и Hash.hash, поскольку именно это Array.uniq запрашивает.

Edit: Где я столкнулся с этим в реальном мире, хэши были немного более сложными. Они были результатом анализа JSON с несколькими полями, некоторые из которых также были хешами. У меня был массив тех результатов, которые я хотел отфильтровать по уникальным значениям.

Мне не нравится решение переопределить Hash.eql? и Hash.hash, потому что мне придется либо переопределить Hash глобально, либо переопределить его для каждой записи в моем массиве. Изменение определения Hash для каждой записи было бы обременительным, тем более что внутри каждой записи могут быть вложенные хэши.

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

Использование inject кажется хорошей альтернативой переопределению Hash.

Ответы [ 8 ]

27 голосов
/ 08 октября 2008

Я могу получить то, что хочу, позвонив inject

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }

Это вернет:

[{:a=>1}, {:a=>2}]
17 голосов
/ 11 апреля 2011

Ruby 1.8.7+ вернет то, что вы ожидали:

[{:a=>1}, {:a=>2}, {:a=>1}].uniq
#=> [{:a=>1}, {:a=>2}] 
5 голосов
/ 07 мая 2009

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

Что я имею в виду:

у вас есть массив:

[{:x=>1},{:x=>2},{:x=>3},{:x=>2},{:x=>1}]

Вы сортируете это (#sort_by {|t| t[:x]}) и получаете это:

[{:x=>1}, {:x=>1}, {:x=>2}, {:x=>2}, {:x=>3}]

Теперь немного изменён вариант ответа Аарона Хинни:

your_array.inject([]) do |result,item| 
  result << item if !result.last||result.last[:x]!=item[:x]
  result
end

Я также пробовал:

test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]}

но это очень медленно. вот мой тест:

test=[]
1000.times {test<<{:x=>rand}}

Benchmark.bmbm do |bm|
  bm.report("sorting: ") do
    test.sort_by {|t| t[:x]}.inject([]) {|r,h| r<<h if !r.last||r.last[:x]!=h[:x]; r}
  end
  bm.report("inject: ") {test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]} }
end

Результаты:

Rehearsal ---------------------------------------------
sorting:    0.010000   0.000000   0.010000 (  0.005633)
inject:     0.470000   0.140000   0.610000 (  0.621973)
------------------------------------ total: 0.620000sec

                user     system      total        real
sorting:    0.010000   0.000000   0.010000 (  0.003839)
inject:     0.480000   0.130000   0.610000 (  0.612438)
3 голосов
/ 08 октября 2008

Предполагая, что ваши хэши - это всегда одиночные пары ключ-значение, это будет работать:

a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}}

Hash.to_a создает массив массивов ключ-значение, поэтому первая карта дает вам:

[[:a, 1], [:a, 2], [:a, 1]]

uniq на массивах делает то, что вы хотите, давая вам:

[[:a, 1], [:a, 2]]

и затем вторая карта снова объединяет их в хэши.

1 голос
/ 05 января 2015

Вы можете использовать (проверено в ruby ​​1.9.3),

[{a: 1},{a: 2},{a:1}].uniq => [{a:1},{a: 2}]
[{a: 1,b: 2},{a: 2, b: 2},{a: 1, b: 3}].uniq_by {|v| v[:a]} => [{a: 1,b: 2},{a: 2, b: 2}]
0 голосов
/ 07 ноября 2012

Метод pipe для массивов (доступен с 1.8.6) выполняет объединение множеств (возвращая массив), поэтому следующий возможный способ получить уникальные элементы любого массива a:

[] | a

0 голосов
/ 06 марта 2009
0 голосов
/ 08 октября 2008

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

...