ruby on rails - que - Uniq por atributo de objeto en Ruby
que es un objeto en ruby (13)
¿Cuál es la manera más elegante de seleccionar objetos en una matriz que son únicos con respecto a uno o más atributos?
Estos objetos se almacenan en ActiveRecord, por lo que usar los métodos de AR también sería bueno.
Agregue el método uniq_by
a Matriz en su proyecto. Funciona por analogía con sort_by
. Entonces 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 quisiera.
EDITAR: Tribalvibes señala que esa implementación es O (n ^ 2). Mejor sería algo así como (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
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 de 1 único, pero lo mismo se puede hacer con ordenamiento lexicográfico ...
Hazlo en el nivel de la base de datos:
YourModel.find(:all, :group => "status")
Implementación de ActiveSupport:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
La forma más elegante que he encontrado es un spin-off usando Array#uniq
con un bloque
enumerable_collection.uniq(&:property)
... ¡también lee mejor!
Me gusta el uso que hace jmah de un Hash para imponer la singularidad. Aquí hay un par de formas más de despellejar 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
Me gustan las respuestas de jmah y Head. ¿Pero conservan orden de matriz? Es posible que en versiones posteriores de ruby ya que ha habido algunos requisitos de preservación de orden de inserción hash escritos en la especificación del lenguaje, pero aquí hay una solución similar que me gusta usar que conserva el orden independientemente.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Originalmente sugerí usar el método de select
en Array. Esto es:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
nos devuelve [2,4,6]
.
Pero si quieres el primer objeto, usa detect
.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
nos da 4
.
Aunque no estoy seguro de a qué te refieres.
Puede usar este truco para seleccionar elementos únicos de varios atributos de la matriz:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
Puede usar un hash, que contiene solo un valor para cada clave:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails también tiene un método #uniq_by - ver Array # parametrizada uniq (es decir, uniq_by)
Si entiendo tu pregunta correctamente, he abordado este problema utilizando el enfoque cuasi-hacky de comparar los objetos Marshaled para determinar si los 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
Use Array#uniq
con un bloque:
@photos = @photos.uniq { |p| p.album_id }