Réponses:
Utiliser Array#uniqavec un bloc:
@photos = @photos.uniq { |p| p.album_id }
require 'backports':-)
@photos.uniq &:album_id
Ajoutez la uniq_byméthode à Array dans votre projet. Cela fonctionne par analogie avec sort_by. Il en uniq_byva de même pour uniqtel sort_byquel sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
La mise en oeuvre:
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
Notez qu'il renvoie un nouveau tableau plutôt que de modifier votre tableau actuel sur place. Nous n'avons pas écrit de uniq_by!méthode mais cela devrait être assez simple si vous le souhaitez.
EDIT: Tribalvibes souligne que cette implémentation est O (n ^ 2). Mieux vaut quelque chose comme (non testé) ...
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
Vous pouvez utiliser cette astuce pour sélectionner unique par plusieurs éléments d'attributs du tableau:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
J'avais initialement suggéré d'utiliser la selectméthode sur Array. En être témoin:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
nous rend [2,4,6].
Mais si vous voulez le premier objet de ce type, utilisez detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}nous donne 4.
Je ne suis pas sûr de ce que vous faites ici, cependant.
J'aime l'utilisation par jmah d'un Hash pour renforcer l'unicité. Voici quelques autres façons d'écorcher ce chat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
C'est un joli 1-liner, mais je suppose que cela pourrait être un peu plus rapide:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Si je comprends bien votre question, j'ai abordé ce problème en utilisant l'approche quasi-hacky de comparaison des objets Marshaled pour déterminer si des attributs varient. L'injection à la fin du code suivant serait un exemple:
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
Le moyen le plus élégant que j'ai trouvé est un spin-off utilisant Array#uniqavec un bloc
enumerable_collection.uniq(&:property)
… Ça se lit mieux aussi!
Utilisez Array # uniq avec un bloc:
objects.uniq {|obj| obj.attribute}
Ou une approche plus concise:
objects.uniq(&:attribute)
Rails a également une #uniq_byméthode.
Référence: Parameterized Array # uniq (ie, uniq_by)
J'aime les réponses de Jmah et Head. Mais préservent-ils l'ordre des tableaux? Ils pourraient le faire dans les versions ultérieures de ruby car il y a eu des exigences de préservation de l'ordre d'insertion de hachage écrites dans la spécification du langage, mais voici une solution similaire que j'aime utiliser qui préserve l'ordre malgré tout.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Implémentation ActiveSupport:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Maintenant, si vous pouvez trier les valeurs d'attribut, cela peut être fait:
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
C'est pour un attribut unique, mais la même chose peut être faite avec le tri lexicographique ...