Attributo Uniq by object in Ruby

Qual è il modo più elegante per selezionare gli oggetti in un array che sono unici rispetto a uno o più attributi?

Questi oggetti sono archiviati in ActiveRecord quindi anche i metodi di AR andrebbero bene.

Usa l’ Array#uniq con un blocco:

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

Aggiungi il metodo uniq_by a Array nel tuo progetto. Funziona per analogia con sort_by . Quindi uniq_by è per uniq come sort_by è per sort . Uso:

 uniq_array = my_array.uniq_by {|obj| obj.id} 

L’implemento:

 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 

Si noti che restituisce una nuova matrice piuttosto che modificare quella corrente in atto. Non abbiamo scritto uniq_by! metodo, ma dovrebbe essere abbastanza facile se lo si desidera.

EDIT: Tribalvibes sottolinea che tale implementazione è O (n ^ 2). Meglio sarebbe qualcosa come (non testato) ...

 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 

Fatelo a livello di database:

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

Inizialmente avevo suggerito di usare il metodo select su Array. A titolo di:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} [1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} ci restituisce [2,4,6] .

Ma se vuoi il primo di questi oggetti, usa detect .

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

Non sono sicuro di cosa stai andando qui, però.

Mi piace l’uso di Jash di un hash per rafforzare l’unicità. Ecco un paio di altri modi per dare alla pelle quel gatto:

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

È un bel transatlantico, ma temo che potrebbe essere un po ‘più veloce:

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

È ansible utilizzare questo trucco per selezionare univoci da diversi attributi di elementi dell’array:

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

Se comprendo correttamente la tua domanda, ho affrontato questo problema usando l’approccio quasi-hacky del confronto degli oggetti Marshall per determinare se gli attributi variano. L’iniezione alla fine del seguente codice sarebbe un esempio:

 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 

Puoi usare un hash, che contiene solo un valore per ogni chiave:

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

Rails ha anche un metodo #uniq_by – vedi Parameterized Array # uniq (ie, uniq_by)

Mi piacciono le risposte di jmah e head. Ma conservano l’ordine di schieramento? Potrebbero in versioni successive di ruby ​​poiché ci sono stati alcuni requisiti di conservazione degli ordini di inserimento degli hash scritti nelle specifiche del linguaggio, ma ecco una soluzione simile che mi piace usare che conserva l’ordine a prescindere.

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

Implementazione di ActiveSupport:

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

Il modo più elegante che ho trovato è uno spin-off usando l’ Array#uniq con un blocco

 enumerable_collection.uniq(&:property) 

… legge anche meglio!

Ora se puoi ordinare sui valori degli attributi, questo può essere fatto:

 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 

Questo è per un attributo 1 unico, ma la stessa cosa può essere fatta con ordinamento lessicografico ...