Respostas:
Use Array#uniq
com um bloco:
@photos = @photos.uniq { |p| p.album_id }
require 'backports'
:-)
@photos.uniq &:album_id
Adicione o uniq_by
método à matriz em seu projeto. Funciona por analogia com sort_by
. Então uniq_by
é uniq
como sort_by
é sort
. Uso:
uniq_array = my_array.uniq_by {|obj| obj.id}
A implementação:
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
Observe que ele retorna uma nova matriz em vez de modificar a atual. Não escrevemos um uniq_by!
método, mas deve ser fácil o suficiente, se você quiser.
EDIT: Tribalvibes indica que essa implementação é O (n ^ 2). Melhor seria algo como (não testado) ...
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
Você pode usar esse truque para selecionar exclusivo por vários elementos de atributos da matriz:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
Originalmente, eu sugeri o uso do select
método Array. A saber:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
nos [2,4,6]
devolve.
Mas se você quiser o primeiro objeto desse tipo, use detect
.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}
nos dá 4
.
Não tenho certeza do que você está procurando aqui, no entanto.
Se entendi sua pergunta corretamente, resolvi esse problema usando a abordagem quase hacky de comparar os objetos Marshaled para determinar se algum atributo varia. A injeção no final do código a seguir seria um exemplo:
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
A maneira mais elegante que encontrei é um spin-off usando Array#uniq
um bloco
enumerable_collection.uniq(&:property)
… Lê melhor também!
Use a Matriz # uniq com um bloco:
objects.uniq {|obj| obj.attribute}
Ou uma abordagem mais concisa:
objects.uniq(&:attribute)
O Rails também possui um #uniq_by
método.
Referência: matriz parametrizada # uniq (ou seja, uniq_by)
Eu gosto das respostas de jmah e Head. Mas eles preservam a ordem dos arrays? Eles podem estar em versões posteriores do ruby, pois existem alguns requisitos de preservação de ordem de inserção de hash gravados na especificação da linguagem, mas aqui está uma solução semelhante que eu gosto de usar que preserva a ordem independentemente.
h = Set.new
objs.select{|el| h.add?(el.attr)}
Implementação do ActiveSupport:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Agora, se você pode classificar os valores dos atributos, isso pode ser feito:
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
Isso é exclusivo para um atributo, mas a mesma coisa pode ser feita com classificação lexicográfica ...