Domanda

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

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

È stato utile?

Soluzione

Usa Array # uniq con un blocco:

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

Altri suggerimenti

Aggiungi il metodo uniq_by all'array nel tuo progetto. Funziona per analogia con sort_by . Quindi uniq_by significa uniq come sort_by è sort . Uso:

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

L'implementazione:

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

Nota che restituisce un nuovo array invece di modificare quello attuale in atto. Non abbiamo scritto un metodo uniq_by! ma dovrebbe essere abbastanza semplice se lo desideri.

EDIT: Tribalvibes sottolinea che l'implementazione è O (n ^ 2). Meglio sarebbe qualcosa di simile (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

Fallo a livello di database:

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

Puoi usare questo trucco per selezionare univoco per diversi elementi di attributi dall'array:

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

Inizialmente avevo suggerito di utilizzare il metodo select su Array. In parole povere:

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

Ma se vuoi il primo oggetto del genere, usa detect .

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

Non sono sicuro di cosa tu voglia fare qui, tuttavia.

Mi piace l'uso da parte di jmah di un hash per rafforzare l'unicità. Ecco un altro paio di modi per scuoiare quel gatto:

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

È un bel 1-liner, ma sospetto che potrebbe essere un po 'più veloce:

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

Se capisco correttamente la tua domanda, ho affrontato questo problema usando l'approccio quasi disordinato del confronto degli oggetti Marshaled per determinare se alcuni 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

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

enumerable_collection.uniq(&:property)

... anche meglio!

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

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

Rails ha anche un metodo #uniq_by - vedi Array con parametri # uniq (ie, uniq_by)

Mi piacciono le risposte di jmah e Head. Ma mantengono l'ordine dell'array? Potrebbero essere nelle versioni successive di ruby ??poiché ci sono stati alcuni requisiti di conservazione dell'ordine di inserimento dell'hash scritti nelle specifiche del linguaggio, ma ecco una soluzione simile che mi piace usare che conserva l'ordine indipendentemente.

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

Ora, se puoi ordinare i valori degli attributi, puoi farlo:

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 unico attributo 1, ma la stessa cosa può essere fatta con un ordinamento lessicografico ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top