2008-09-20 12 views
100

Was ist die eleganteste Art, Objekte in einem Array auszuwählen, die in Bezug auf ein oder mehrere Attribute einzigartig sind?Uniq nach Objektattribut in Ruby

Diese Objekte werden in ActiveRecord gespeichert, sodass die Verwendung der AR-Methoden auch in Ordnung wäre.

Antwort

156

Verwenden Array#uniq mit einem Block:

@photos = @photos.uniq { |p| p.album_id } 
+4

Dies ist die richtige Antwort für [ruby 1.9] (http://ruby-doc.org/core-1.9.2/Array.html#method-i-uniq) und spätere Versionen. – nurettin

+2

+1. Und für frühere Rubine gibt es immer 'require' Backports' :-) –

+0

Die Hash-Methode ist besser, wenn du mit say album_id gruppieren willst während du (say) summierst num_plays. – thekingoftruth

6

Ich hatte ursprünglich vorgeschlagen, die select-Methode auf Array zu verwenden. Um zu schreiben:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} gibt uns [2,4,6] zurück.

Wenn Sie jedoch das erste Objekt wünschen, verwenden Sie detect.

[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gibt uns 4.

Ich bin mir nicht sicher, was Sie hier für, aber though.

+0

+1 Für die Erkennungsmethode, niemals bewusst sein. – pierrotlefou

3

Wenn ich Ihre Frage richtig verstanden habe, habe ich dieses Problem mit dem quasi-Hacky-Ansatz des Vergleichs der Marshalled-Objekte angegangen, um zu bestimmen, ob irgendwelche Attribute variieren. Die inject am Ende des folgenden Codes wäre ein Beispiel:

class Foo 
    attr_accessor :foo, :bar, :baz 

    def initialize(foo,bar,baz) 
    @foo = foo 
    @bar = bar 
    @baz = baz 
    end 
end 

objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)] 

# find objects that are uniq with respect to attributes 
objs.inject([]) do |uniqs,obj| 
    if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) } 
    uniqs << obj 
    end 
    uniqs 
end 
0

Nun, wenn Sie auf die Attributwerte sortieren können dies getan werden kann:

class A 
    attr_accessor :val 
    def initialize(v); self.val = v; end 
end 

objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)} 

objs.sort_by{|a| a.val}.inject([]) do |uniqs, a| 
    uniqs << a if uniqs.empty? || a.val != uniqs.last.val 
    uniqs 
end 

Das ist für ein 1-Attribut einzigartig, aber das gleiche kann man w/lexikographischer Art geschehen ...

13

Machen sie es auf der Datenbank-Ebene:

YourModel.find(:all, :group => "status") 
+1

und was, wenn es mehr als ein Feld war, aus Interesse? –

2

Sie können einen Hash verwenden, die nur einen Wert für jeden Schlüssel enthält:

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values 
20

Fügen Sie die uniq_by Methode Array in Ihrem Projekt. Es funktioniert in Analogie zu sort_by. So ist uniq_by zu uniq als sort_by zu sort. Verbrauch:

uniq_array = my_array.uniq_by {|obj| obj.id} 

Die Umsetzung:

class Array 
    def uniq_by(&blk) 
    transforms = [] 
    self.select do |el| 
     should_keep = !transforms.include?(t=blk[el]) 
     transforms << t 
     should_keep 
    end 
    end 
end 

Beachten Sie, dass es ein neues Array zurückgibt, anstatt Ihre aktuelle anstelle ändern. Wir haben keine uniq_by! Methode geschrieben, aber es sollte einfach genug sein, wenn Sie es wollten.

EDIT: Tribalvibes weist darauf hin, dass diese Implementierung ist O (n^2). Besser wäre sowas wie (ungetestet) ...

class Array 
    def uniq_by(&blk) 
    transforms = {} 
    select do |el| 
     t = blk[el] 
     should_keep = !transforms[t] 
     transforms[t] = true 
     should_keep 
    end 
    end 
end 
+1

Schöne API, aber das wird schlechte (wie O (n^2)) Skalierungsleistung für große Arrays haben. Könnte durch Transformieren eines Hashsets behoben werden. – tribalvibes

+6

Diese Antwort ist veraltet. Ruby> = 1.9 hat Array # uniq mit einem Block, der genau das tut, wie in der angenommenen Antwort. –

5

Ich mag jmahs Verwendung eines Hashes, um Eindeutigkeit zu erzwingen.Hier ein paar mehr Möglichkeiten, um die Haut, die Katze:

objs.inject({}) {|h,e| h[e.attr]=e; h}.values 

Das ist ein schöner 1-liner, aber ich vermute, dies könnte ein wenig schneller sein:

h = {} 
objs.each {|e| h[e.attr]=e} 
h.values 
1

Ich mag Jmah und Head's Antworten. Aber behalten sie die Array-Reihenfolge bei? Sie könnten in späteren Versionen von Ruby, da einige Hash-Einfügungs-Order-Erhaltung Anforderungen in die Sprachspezifikation geschrieben wurden, aber hier ist eine ähnliche Lösung, die ich gerne verwenden, die Reihenfolge unabhängig bleibt.

h = Set.new 
objs.select{|el| h.add?(el.attr)} 
1

Active Umsetzung:

def uniq_by 
    hash, array = {}, [] 
    each { |i| hash[yield(i)] ||= (array << i) } 
    array 
end 
4

können Sie diesen Trick verwenden, um mehrere Attribute Elemente von Array einzigartig zu wählen:

@photos = @photos.uniq { |p| [p.album_id, p.author_id] } 
0

Die eleganteste Art, wie ich gefunden habe, ist ein Spin-off Array#uniq mit einem Block

enumerable_collection.uniq(&:property) 

... es liest sich besser zu verwenden!

Verwandte Themen