Ответ 1
Я могу получить то, что хочу, вызывая inject
a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }
Это вернет:
[{:a=>1}, {:a=>2}]
У меня есть массив хэшей, и я хочу получить уникальные значения. Вызов 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
.
Изменить: Где я столкнулся с этим в реальном мире, хеши были немного сложнее. Они были результатом разобранного JSON, который имел несколько полей, некоторые из которых были также значениями хэшей. У меня был массив этих результатов, которые я хотел отфильтровать уникальные значения.
Мне не нравится решение redefine Hash.eql?
и Hash.hash
, потому что мне нужно либо переопределить Hash
глобально, либо переопределить его для каждой записи в моем массиве. Изменение определения Hash
для каждой записи было бы громоздким, тем более, что внутри каждой записи могут быть вложенные хэши.
Изменение Hash
во всем мире имеет некоторый потенциал, особенно если это было сделано временно. Я хотел бы создать еще один класс или вспомогательную функцию, которая завершает сохранение старых определений и восстанавливает их, но я думаю, что это добавляет больше сложности, чем это действительно необходимо.
Использование inject
кажется хорошей альтернативой переопределению Hash
.
Я могу получить то, что хочу, вызывая inject
a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }
Это вернет:
[{:a=>1}, {:a=>2}]
Ruby 1.8.7+ вернет только то, что вы ожидали:
[{:a=>1}, {:a=>2}, {:a=>1}].uniq
#=> [{:a=>1}, {:a=>2}]
У меня была аналогичная ситуация, но у хэшей были ключи. Я использовал метод сортировки.
Что я имею в виду:
у вас есть массив:
[{: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)
Предполагая, что ваши хеши всегда представляют собой пары с одним ключом, это будет работать:
a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}}
Hash.to_a создает массив массивов с ключом, поэтому первая карта получает вас:
[[:a, 1], [:a, 2], [:a, 1]]
uniq on Arrays делает то, что вы хотите, давая вам:
[[:a, 1], [:a, 2]]
а затем вторая карта снова помещает их обратно как хэши.
Вы можете использовать (проверено в 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}]
Ответ, который вы даете, аналогичен приведенному ниже здесь. Он переопределяет методы hash
и eql?
для хэшей, которые должны появляться в массиве, а затем корректно ведет себя uniq
.
найдено в google http://mikeburnscoder.wordpress.com/2008/01/18/uniquify-an-array-of-hashes-in-ruby/
Метод pipe на массивах (доступный с версии 1.8.6) выполняет команду set union (возврат массива), поэтому следующий способ - получить уникальные элементы любого массива a
:
[] | a