Как мне получить уникальные элементы из массива хэшей в Ruby?
Вопрос
У меня есть массив хэшей, и я хочу извлечь из него уникальные значения.Зовущий 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, который содержал несколько полей, некоторые из значений которых также были хэшами.У меня был массив этих результатов, которые я хотел отфильтровать по уникальным значениям.
Мне не нравится это переопределение 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 в массивах делает то, что вы хотите, давая вам:
[[: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
ведите себя правильно.
Метод pipe для массивов (доступен с версии 1.8.6) выполняет объединение множеств (возвращает массив), поэтому ниже приведен еще один возможный способ получения уникальных элементов любого массива a
:
[] | a