Réponses:
Utiliser Array#uniq
avec un bloc:
@photos = @photos.uniq { |p| p.album_id }
require 'backports'
:-)
@photos.uniq &:album_id
Ajoutez la uniq_by
méthode à Array dans votre projet. Cela fonctionne par analogie avec sort_by
. Il en uniq_by
va de même pour uniq
tel sort_by
quel 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 select
mé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#uniq
avec 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_by
mé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 ...