2008-10-08 14 views
24

Ich habe eine Reihe von Hashes, und ich möchte die einzigartigen Werte daraus. Anrufen gibt mir nicht, was ich erwarte.Wie bekomme ich die einzigartigen Elemente aus einem Array von Hashes in Ruby?

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

Wo ich erwartet hatte:

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

im Netz Bei der Suche um, habe ich nicht kommen mit einer Lösung, die ich mit war glücklich. Leute empfohlen, Hash.eql? und Hash.hash neu zu definieren, da das ist, was Array.uniq abfragt.

Edit: Wo ich in der realen Welt darauf stieß, waren die Hashes etwas komplexer. Sie waren das Ergebnis von geparsten JSONs, die mehrere Felder hatten, von denen einige auch Hashwerte enthielten. Ich hatte eine Reihe von Ergebnissen, die ich herausfiltern wollte.

Ich mag nicht die neu zu definieren Hash.eql? und Hash.hash Lösung, weil ich entweder Hash neu zu definieren global würde, oder es in meinem Array für jeden Eintrag neu zu definieren. Es wäre umständlich, die Definition von Hash für jeden Eintrag zu ändern, insbesondere da in jedem Eintrag verschachtelte Hashes vorhanden sein können.

Das Ändern von Hash hat weltweit ein gewisses Potenzial, vor allem, wenn es vorübergehend durchgeführt wurde. Ich möchte eine weitere Klassen- oder Hilfsfunktion erstellen, die die alten Definitionen speichert und wiederherstellt, aber ich denke, das fügt mehr Komplexität hinzu, als wirklich benötigt wird.

Die Verwendung von inject scheint eine gute Alternative zur Neudefinition Hash zu sein.

Antwort

27

kann ich bekommen, was ich will von inject

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

Aufruf dieser zurück:

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

viel besser denke ich, als die Verbindung, die ich oben – edthix

0

Die Antwort, die Sie geben, ist ähnlich der einer here diskutiert. Es überschreibt die Methoden hash und für die Hashes, die im Array erscheinen sollen, was dazu führt, dass sich uniq korrekt verhält.

+0

gepostet, dass eine der Lösungen, die ich im Netz gefunden. Ich mochte es nicht, dass ich Hash neu definieren musste, nur um uniq zu nennen. –

+0

Wenn die Vanilla-Hash- und Array-Klassen nicht das tun, was Sie brauchen, sollten Sie in Betracht ziehen, eigene Klassen zu definieren, die das erforderliche Verhalten implementieren. Können Sie beschreiben, was Sie mit Arrays von Hashes zu modellieren versuchen? –

2

Angenommen, Ihre Hashes sind immer einzelne Schlüssel-Wert-Paare wird diese Arbeit:

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

Hash.to_a eine Reihe von Schlüssel-Wert-Arrays erstellt, so dass die erste Karte bekommt man:

[[: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 zurück togeth ähm als Hashes.

+0

Das reale Problem, auf das ich stieß, verwendete komplexere Hashes. –

+0

Nicht sicher, warum dies abgelehnt wurde, also habe ich es wieder aufgesetzt. –

5

Ich habe eine ähnliche Situation habe gefunden, aber Hashes Schlüssel hatte. Ich habe die Sortiermethode verwendet.

Was ich meine:

Sie ein Array haben:

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

Sie es sortieren (#sort_by {|t| t[:x]}) und diese:

[{: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 meine Benchmark:

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) 
17

Rubin 1.8.7+ Rückkehr wird genau das, was Sie erwartet haben:

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

Das Rohr-Methode auf Arrays (verfügbar seit 1.8 .6) Führt eine Vereinigungsmenge aus (ein Array wird zurückgegeben), daher ist das Folgende eine weitere Möglichkeit, um eindeutige Elemente eines Arrays zu erhalten: :

[] | a

+0

das funktioniert nicht für mich. –

+0

@SunnyRGupta, welche Version von Ruby verwenden Sie? – yoniLavi

+0

'Ruby 1.9.3p448 (2013-06-27 Revision 41675) [x86_64-darwin13.2.0]' –

1

Sie verwenden können (in Ruby getestet 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}]