Antworten:
Verwendung Array#uniq
mit einem Block:
@photos = @photos.uniq { |p| p.album_id }
require 'backports'
:-)
@photos.uniq &:album_id
Fügen Sie die uniq_by
Methode Array in Ihrem Projekt hinzu. Es funktioniert analog zu sort_by
. So uniq_by
ist es uniq
wie es sort_by
ist sort
. Verwendung:
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 ein neues Array zurückgegeben wird, anstatt das aktuelle Array zu ändern. Wir haben keine uniq_by!
Methode geschrieben , aber es sollte einfach genug sein, wenn Sie wollten.
EDIT: Tribalvibes weist darauf hin, dass diese Implementierung O (n ^ 2) ist. Besser wäre so etwas 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
Mit diesem Trick können Sie eindeutige Elemente mit mehreren Attributen aus dem Array auswählen:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
Ich hatte ursprünglich vorgeschlagen, die select
Methode auf Array zu verwenden. Nämlich:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gibt uns [2,4,6]
zurück.
Wenn Sie jedoch das erste derartige Objekt möchten, verwenden Sie detect
.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
gibt uns 4
.
Ich bin mir allerdings nicht sicher, was Sie hier vorhaben.
Ich mag es, wenn jmah einen Hash verwendet, um die Einzigartigkeit durchzusetzen. Hier sind noch ein paar Möglichkeiten, diese Katze zu häuten:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
Das ist ein schöner 1-Liner, aber ich vermute, dass dies etwas schneller sein könnte:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Wenn ich Ihre Frage richtig verstehe, habe ich dieses Problem mithilfe des quasi-hackigen Ansatzes gelöst, bei dem die Marshaled-Objekte verglichen werden, um festzustellen, ob Attribute variieren. Die Injektion 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
Der eleganteste Weg, den ich gefunden habe, ist ein Spin-off Array#uniq
mit einem Block
enumerable_collection.uniq(&:property)
… Es liest sich auch besser!
Verwenden Sie Array # uniq mit einem Block:
objects.uniq {|obj| obj.attribute}
Oder ein prägnanterer Ansatz:
objects.uniq(&:attribute)
Schienen hat auch eine #uniq_by
Methode.
Ich mag die Antworten von jmah und Head. Aber behalten sie die Array-Reihenfolge bei? Sie könnten in späteren Versionen von Ruby verwendet werden, da einige Anforderungen zur Beibehaltung der Reihenfolge der Hash-Einfügung in die Sprachspezifikation geschrieben wurden, aber hier ist eine ähnliche Lösung, die ich gerne verwende und die die Reihenfolge unabhängig davon beibehält.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Wenn Sie nun nach den Attributwerten sortieren können, können Sie Folgendes tun:
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 mit lexikographischer Sortierung gemacht werden ...