Pergunta

O que é a maneira mais elegante para selecionar a objetos em uma matriz que são únicos em relação a um ou mais atributos?

Esses objetos são armazenados em ActiveRecord isso usando métodos da AR seria bom também.

Foi útil?

Solução

Use Array#uniq com um bloco:

@photos = @photos.uniq { |p| p.album_id }

Outras dicas

Adicione o método uniq_by a matriz em seu projeto. Ele 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

Note que ele retorna uma nova matriz em vez de modificar seu atual no lugar. Nós não ter escrito um método uniq_by! mas deve ser fácil o suficiente se você queria.

EDIT: aponta Tribalvibes a que a aplicaçã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

Faça-o no nível de banco de dados:

YourModel.find(:all, :group => "status")

Você pode usar esse truque para selecionar exclusivo por vários atributos elementos do array:

@photos = @photos.uniq { |p| [p.album_id, p.author_id] }

Eu tinha inicialmente sugerido pelo método select na Matriz. A saber:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} dá-nos [2,4,6] de volta.

Mas se você quer o primeiro tal objeto, o uso detect.

[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} nos dá 4.

Eu não sei o que você está indo para aqui, no entanto.

I como o uso de JMAH de um Hash para impor exclusividade. Aqui está mais um par de maneiras de pele que gato:

objs.inject({}) {|h,e| h[e.attr]=e; h}.values

Isso é um nice 1-liner, mas eu suspeito que isso pode ser um pouco mais rápido:

h = {}
objs.each {|e| h[e.attr]=e}
h.values

Se eu entendi sua pergunta, eu tenho abordado este problema utilizando a abordagem quasi-hacky de comparar o empacotado objetos para determinar se todos os atributos variar. O injectar no final do seguinte código 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 forma mais elegante que eu encontrei é um spin-off usando Array#uniq com um bloco

enumerable_collection.uniq(&:property)

... ele lê muito melhor!

Você pode usar um hash, que contém apenas um valor para cada chave:

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values

Rails também tem um método #uniq_by - veja Parameterized Array # uniq (ie, uniq_by)

Gosto JMAH e respostas da Cabeça. Mas será que eles preservar a ordem matriz? Eles podem em versões posteriores do rubi já que houve algumas de hash de inserção-de preservação da ordem requisitos escritos na especificação da linguagem, mas aqui está uma solução similar que eu gosto de usar que preserva a ordem independentemente.

h = Set.new
objs.select{|el| h.add?(el.attr)}

implementação ActiveSupport:

def uniq_by
  hash, array = {}, []
  each { |i| hash[yield(i)] ||= (array << i) }
  array
end

Agora, se você pode classificar os valores de atributo 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 é um atributo-1 única, mas a mesma coisa pode ser feito w / lexicographical tipo ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top