web-dev-qa-db-fra.com

Uniq par attribut d'objet dans Ruby

Quelle est la manière la plus élégante de sélectionner des objets dans un tableau qui sont uniques par rapport à un ou plusieurs attributs?

Ces objets sont stockés dans ActiveRecord, donc utiliser les méthodes AR serait bien aussi.

114
sutee

Utilisation Array#uniq avec un bloc:

@photos = @photos.uniq { |p| p.album_id }
187
张健健

Ajouter le uniq_by à Array dans votre projet. Cela fonctionne par analogie avec sort_by. Alors uniq_by est à uniq comme sort_by est à 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 retourne un nouveau tableau plutôt que de modifier votre actuel en place. Nous n'avons pas écrit de uniq_by! mais elle devrait être assez simple si vous le souhaitez.

EDIT: Tribalvibes souligne que cette implémentation est O (n ^ 2). Mieux serait 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
21
Daniel Lucraft

Faites-le au niveau de la base de données:

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

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] }
8
YauheniNinja

J'avais initialement suggéré d'utiliser la méthode select sur Array. En être témoin:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} nous donne [2,4,6] retour.

Mais si vous voulez le premier de ces objets, utilisez detect.

[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} nous donne 4.

Je ne sais pas ce que vous cherchez ici, cependant.

6
Alex M

J'aime l'utilisation par jmah d'un hachage 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 Nice 1-liner, mais je pense que cela pourrait être un peu plus rapide:

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

Si je comprends bien votre question, j'ai résolu 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
3
Drew Olson

La manière la plus élégante que j'ai trouvée est une spin-off utilisant Array#uniq avec un bloc

enumerable_collection.uniq(&:property)

… Ça se lit mieux aussi!

3
iGbanam

Rails a également une méthode #uniq_by - voir Tableau paramétré # uniq (c'est-à-dire uniq_by)

2
apb

Vous pouvez utiliser un hachage, qui contient une seule valeur pour chaque clé:

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

J'aime jmah et les réponses de Head. Mais préservent-ils l'ordre des tableaux? Ils pourraient 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)}
1
TKH

Implémentation d'ActiveSupport:

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

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 un tri lexicographique ...

0
Purfideas