Uniq von Objektattribut in Ruby
-
02-07-2019 - |
Frage
Was ist die eleganteste Art und Weise Objekte auswählen aus in einem Array, das in Bezug auf ein oder mehrere Attribute eindeutig sind?
Diese Objekte werden in Active gespeichert, so AR Methoden verwenden wäre auch in Ordnung.
Lösung
Verwenden Sie Array#uniq
mit einem Block:
@photos = @photos.uniq { |p| p.album_id }
Andere Tipps
Fügen Sie die uniq_by
Methode Array in Ihrem Projekt. Es funktioniert analog zu sort_by
. So uniq_by
ist zu uniq
als sort_by
sort
ist. 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 man will.
EDIT: Tribalvibes weist darauf hin, dass diese Implementierung ist O (n ^ 2). Besser wäre so etwas wie (nicht getestet) ...
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
Machen Sie es auf der Datenbank-Ebene:
YourModel.find(:all, :group => "status")
Sie können diesen Trick verwenden, um mehrere Attribute Elemente von Array einzigartig wählen:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
Ich hatte ursprünglich vorgeschlagen, die select
Methode auf Array verwendet wird. Nämlich:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gibt uns [2,4,6]
zurück.
Aber wenn Sie das erste solches Objekt möchten, verwenden Sie detect
.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
gibt uns 4
.
Ich bin nicht sicher, was Sie hier gehen, though.
Ich mag JMAH die Verwendung einer Hash 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
Wenn ich Ihre Frage richtig verstanden hat, habe ich dieses Problem mit dem quasi-Hacky Ansatz in Angriff genommen, die serialisiert Objekte zu vergleichen, um zu bestimmen, ob irgendwelche Attribute variieren. Die inject am Ende des folgenden Codes wären 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
Die eleganteste Art, wie ich gefunden habe, ist ein Spin-off mit Array#uniq
mit einem Block
enumerable_collection.uniq(&:property)
... es liest auch besser!
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
Rails hat auch eine #uniq_by Methode - Parameterized Array # uniq sehen (dh uniq_by)
Ich mag JMAH und Head Antworten. Aber erhalten sie Array Ordnung? Sie könnten in späteren Versionen von Rubin, da es einige Hash-Insertion Ordnung erhalt Anforderungen in die Sprache Spezifikation geschrieben, aber hier ist eine ähnliche Lösung, die Ich mag, dass, um unabhängig bewahrt verwenden.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Active Umsetzung:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Nun, wenn Sie auf die Attributwerte sortieren kann 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 ...