2013-12-13 20 views
6

Ich habe zahlreiche Fragen dazu gesehen, aber nur mit einem Schlüssel, niemals für mehrere Schlüssel.Ruby entfernen doppelte Einträge in Array von Hashes, aber basierend auf mehr als einem Wert

Ich habe folgendes Array von Hashes:

a = [{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"3:21"}, 
{:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
{:name=>"Luv Is", :duration=>"3:13"}, 
{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"2"}, 
{:name=>"Chick on the Side", :artist=>"Another Dude"}] 

a.uniq wird hier nicht funktionieren, weil die Dauer unterschiedlich ist oder vielleicht nicht einmal existieren. Ich habe einen eindeutigen Schlüssel in der Datenbank eingerichtet, der keine doppelten Einträge mit demselben Namen, Interpret und Composer zulässt, so dass ich manchmal Fehler erhalte, wenn Leute doppelte Einträge für diese 3 Schlüssel haben.

Gibt es eine Möglichkeit, uniq zu laufen, die für diese 3 Schlüssel prüfen würde? Ich habe versucht, einen Block wie folgt aus:

new_tracks.uniq do |a_track| 
    a_track[:name] 
    a_track[:artist] 
    a_track[:composer] 
end 

Aber das alles ignoriert, wenn der Schlüssel nicht vorhanden ist (jeder Eintrag ohne Komponist die oben genannten Kriterien zum Beispiel nicht erfüllt).

Ich könnte immer nur den :name Schlüssel verwenden, aber das würde bedeuten, dass ich potenziell gültige Titel in Compilations loswerde, die denselben Titel, aber einen anderen Interpreten oder Komponisten haben.

Dies ist mit Ruby 2.0.

Antwort

13

uniq akzeptiert einen Block. Wenn ein Block angegeben wird, wird der Rückgabewert des Blocks zum Vergleich verwendet.

Ihr Code war nah an der Lösung, aber in Ihrem Code war der Rückgabewert nur a_track[:composer], die die letzte ausgewertete Aussage ist.

Sie können die gewünschten Attribute in eine Zeichenfolge einfügen und diese Zeichenfolge zurückgeben.

new_tracks.uniq { |track| [track[:name], track[:artist], track[:composer]].join(":") } 

Eine mögliche Refactoring ist

new_tracks.uniq { |track| track.attributes.slice('name', 'artist', 'composer').values.join(":") } 

Oder eine benutzerdefinierte Methode in Ihrem Modell hinzufügen, die die Verknüpfung durchführt, und nennen es

class Track < ActiveRecord::Base 
    def digest 
    attributes.slice('name', 'artist', 'composer').values.join(":") 
    end 
end 

new_tracks.uniq(&:digest) 
+0

Großartig verwenden möchten, ** vielen Dank! ** Die erste funktioniert gut: '.uniq {| track | [Titel [: Name], Titel [: Künstler], Titel [: Komponist]]. join (":")} '. Der zweite gibt mir einen Fehler 'SyntaxError: unerwartet '}', erwartet ']''. Wenn ich das behebe, dann bekomme ich 'undefined Methode' Attribute''. Aber der Erste macht den Trick. Danke noch einmal. – kakubei

+0

Ich habe den Syntaxfehler behoben. –

+0

Ich bekomme immer noch undefined Methode 'Attribute'' für diese Zeile ... – kakubei

2

Wenn ich verstehe Ihre Frage, es ist nur ein Frage der Verwendung der richtigen Kombination von Daten innerhalb der uniq Block:

a = [ 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"3:21"}, 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"2"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude"}, 
    {:name=>"Luv Is", :duration=>"3:13"}, 
] 

a.uniq{ |a_track| 
    [ 
    a_track[:name], 
    a_track[:artist], 
    a_track[:composer], 
    ] 
} 

Welche zurückkehren würde:

[ 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=>"First Dude", :duration=>"3:21"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
    {:name=>"Luv Is", :duration=>"3:13"} 
] 

uniq lässt uns etwas in seinem Block erstellen, und verwendet diese für den Vergleich. Ich wähle einen Array zu verwenden, da Ruby-weiß, wie Arrays zu vergleichen, aber der Wert könnte ein MD5-Prüfsumme oder eine CRC-Prüfung, wenn das sinnvoll:

a.uniq{ |a_track| 
    OpenSSL::Digest::MD5.digest(a_track[:name] + (a_track[:artist] || '') + (a_track[:composer] || '')) 
} 
# => [{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=>"First Dude", :duration=>"3:21"}, {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, {:name=>"Luv Is", :duration=>"3:13"}] 

Ich habe (a_track[:artist] || '') zu verwenden, da wir können‘ t verketten Sie einen nil zu einem String, so || '' gibt stattdessen eine leere Zeichenfolge zurück.

+0

Das ist interessant, ich mag diesen Ansatz: 'a.uniq {| a_track | [a_track [: name], a_track [: artist], a_track [: komponist]]} 'Ich habe Simones Antwort zuerst gesehen, also akzeptierte ich das als Antwort, aber mir gefällt diese besser. Danke vielmals. – kakubei

+0

Sie können auch jeden Wert "to_s", der alle Nils in leere Strings konvertieren soll. –

+0

Wir können, aber es versteckt Absicht. –

0

Eine andere Möglichkeit ist die Verwendung von values_at.Wenn Sie nicht Slice und Join

a.uniq {|hash| hash.values_at(:name, :composer, :artist)}