Wie kann ich die einzigartigen Elemente aus einem Array von Hashes in Ruby bekommen?
Frage
Ich habe ein Array von Hashes, und ich möchte die eindeutigen Werte aus ihm heraus. Array.uniq
Aufruf nicht mir geben, was ich erwarte.
a = [{:a => 1},{:a => 2}, {:a => 1}]
a.uniq # => [{:a => 1}, {:a => 2}, {:a => 1}]
Wo ich erwartet hatte:
[{:a => 1}, {:a => 2}]
auf dem Netz der Suche um, ich habe nicht kommen mit einer Lösung, die ich war zufrieden mit. Leute empfohlen Hash.eql?
und Hash.hash
neu zu definieren, denn das ist, was Array.uniq
abfragt.
Edit: Wo ich in diesen in der realen Welt lief, waren die Hashes etwas komplexer. Sie waren das Ergebnis von geparsten JSON, die mehrere Felder hatte, von denen einige die Werte Hashes auch waren. Ich hatte eine Reihe von diesen Ergebnissen, dass ich die eindeutigen Werte herauszufiltern wollte.
Ich mag es nicht, die neu definieren Hash.eql?
und Hash.hash
Lösung, weil ich entweder Hash
global neu zu definieren müsste, oder neu zu definieren es für jeden Eintrag in meinem Array. Änderung der Definition von Hash
für jeden Eintrag wäre umständlich, zumal es Hashes innerhalb jedes Eintrags verschachtelt werden können.
Ändern Hash
global hat ein gewisses Potenzial, vor allem, wenn es vorübergehend durchgeführt wurden. Ich würde will eine andere Klasse oder Hilfsfunktion erstellen, die die alten Definitionen eingewickelt Speicher aus, und die Wiederherstellung sie, aber ich denke, das mehr Komplexität fügt als wirklich benötigt wird.
inject
Verwendung scheint wie eine gute Alternative Hash
neu zu definieren.
Lösung
Ich kann bekommen, was ich will von inject
Aufruf
a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }
Dies wird zurück:
[{:a=>1}, {:a=>2}]
Andere Tipps
Ruby-1.8.7+ wird wieder genau das, was Sie erwartet haben:
[{:a=>1}, {:a=>2}, {:a=>1}].uniq
#=> [{:a=>1}, {:a=>2}]
Ich habe eine ähnliche Situation gehabt, aber Hashes hatte Schlüssel. Ich benutzte Sortiermethode.
Was ich meine:
Sie haben ein Array:
[{:x=>1},{:x=>2},{:x=>3},{:x=>2},{:x=>1}]
Sie sortieren sie (#sort_by {|t| t[:x]}
) und erhalten so aus:
[{:x=>1}, {:x=>1}, {:x=>2}, {:x=>2}, {:x=>3}]
jetzt ein bisschen modifizierte Version Antwort von Aaaron Hinni:
your_array.inject([]) do |result,item|
result << item if !result.last||result.last[:x]!=item[:x]
result
end
Ich habe auch versucht:
test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]}
, aber es ist sehr langsam. hier ist mein Maßstab:
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
Ergebnisse:
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)
Angenommen, Ihre Hashes sind immer einzelne Schlüssel-Wert-Paare, dies funktionieren wird:
a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}}
Hash.to_a erstellt ein Array von Schlüssel-Wert-Arrays, so dass die erste Karte, die Sie bekommt:
[[:a, 1], [:a, 2], [:a, 1]]
uniq auf Arrays tut, was Sie wollen, geben Sie:
[[:a, 1], [:a, 2]]
und dann die zweite Karte setzt sie wieder zusammen als Hashes wieder.
Sie können mit (getestet in 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}]
Die Antwort, die Sie geben, ist ähnlich dem diskutierten hier . Es überschreibt die hash
und eql?
Methoden auf die Hash-Werte, die in der Matrix erscheinen sollen, die dann richtig uniq
verhalten macht.
Die Rohrverfahren auf Arrays (verfügbar seit 1.8.6) führen gesetzt Union (zurückkehr ein Array), so dass der folgende ist eine weitere mögliche Art und Weise einzigartige Elemente jeder Array a
zu erhalten:
[] | a