Respuestas:
Usar Array#uniq
con un bloque:
@photos = @photos.uniq { |p| p.album_id }
require 'backports'
:-)
@photos.uniq &:album_id
Agregue el uniq_by
método a Array en su proyecto. Funciona por analogía con sort_by
. Así uniq_by
es uniq
como sort_by
es sort
. Uso:
uniq_array = my_array.uniq_by {|obj| obj.id}
La implementación:
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
Tenga en cuenta que devuelve una nueva matriz en lugar de modificar la actual en su lugar. No hemos escrito un uniq_by!
método, pero debería ser lo suficientemente fácil si quisieras.
EDITAR: Tribalvibes señala que esa implementación es O (n ^ 2). Mejor sería algo así (no probado) ...
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
Puede usar este truco para seleccionar elementos únicos de varios atributos de la matriz:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
Originalmente había sugerido usar el select
método en Array. Esto es:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
nos [2,4,6]
devuelve
Pero si quieres el primer objeto de este tipo, úsalo detect
.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
nos da 4
.
Sin embargo, no estoy seguro de lo que vas a hacer aquí.
Me gusta el uso que hace Jmah de un Hash para imponer la singularidad. Aquí hay un par de formas más de pelar a ese gato:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
Es un buen 1-liner, pero sospecho que esto podría ser un poco más rápido:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Si entiendo su pregunta correctamente, he abordado este problema utilizando el enfoque casi hacky de comparar los objetos Marshaled para determinar si algunos atributos varían. La inyección al final del siguiente código sería un ejemplo:
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
La forma más elegante que he encontrado es un spin-off usando Array#uniq
un bloque
enumerable_collection.uniq(&:property)
... se lee mejor también!
Use Array # uniq con un bloque:
objects.uniq {|obj| obj.attribute}
O un enfoque más conciso:
objects.uniq(&:attribute)
Rails también tiene un #uniq_by
método.
Referencia: matriz parametrizada # uniq (es decir, uniq_by)
Me gustan las respuestas de JMAH y Head. ¿Pero conservan el orden de la matriz? Es posible que en versiones posteriores de ruby, ya que ha habido algunos requisitos de preservación del orden de inserción hash escritos en la especificación del lenguaje, pero aquí hay una solución similar que me gusta usar que independientemente conserva el orden.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Implementación de ActiveSupport:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Ahora, si puede ordenar los valores de los atributos, esto se puede hacer:
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
Eso es para un atributo 1 único, pero se puede hacer lo mismo con el tipo lexicográfico ...