it-swarm.com.de

Uniq nach Objektattribut in Ruby

Was ist die eleganteste Methode, um Objekte in einem Array auszuwählen, die in Bezug auf ein oder mehrere Attribute eindeutig sind?

Diese Objekte werden in ActiveRecord gespeichert, sodass die Verwendung der AR-Methoden ebenfalls in Ordnung wäre.

114
sutee

Verwenden Array#uniq mit einem Block:

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

Fügen Sie Array in Ihrem Projekt die Methode uniq_by Hinzu. Es funktioniert analog zu sort_by. Also ist uniq_by Zu uniq wie sort_by Zu sort. Verwendung:

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

Die Umsetzung:

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

Beachten Sie, dass ein neues Array zurückgegeben wird, anstatt das aktuelle Array zu ändern. Wir haben keine uniq_by! - Methode geschrieben, aber es sollte einfach genug sein, wenn Sie möchten.

EDIT: Tribalvibes weist darauf hin, dass die Implementierung O (n ^ 2) ist. Besser wäre so etwas wie (ungetestet) ...

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

Tun Sie es auf Datenbankebene:

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

Mit diesem Trick können Sie eindeutige Elemente mit mehreren Attributen aus dem Array auswählen:

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

Ich hatte ursprünglich vorgeschlagen, die Methode select für Array zu verwenden. Nämlich:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} gibt uns [2,4,6] zurück.

Wenn Sie jedoch das erste Objekt dieser Art möchten, verwenden Sie detect.

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

Ich bin mir jedoch nicht sicher, was Sie hier anstreben.

6
Alex M

Ich mag es, wenn Jmah einen Hash benutzt, um die Einzigartigkeit zu erzwingen. Hier sind ein paar weitere Möglichkeiten, um diese Katze zu häuten:

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

Das ist ein netter 1-Liner, aber ich vermute, dass dies etwas schneller sein könnte:

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

Wenn ich Ihre Frage richtig verstehe, habe ich dieses Problem mithilfe des quasi-hackigen Ansatzes des Vergleichs der Marshalled-Objekte gelöst, um festzustellen, ob Attribute variieren. Das Einfügen am Ende des folgenden Codes wäre ein Beispiel:

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

Der eleganteste Weg, den ich gefunden habe, ist ein Spin-off mit Array#uniq mit einem Block

enumerable_collection.uniq(&:property)

… Es liest sich auch besser!

3
iGbanam

Rails hat auch eine #uniq_by-Methode - siehe Parametrisiertes Array # uniq (d. H. Uniq_by)

2
apb

Sie können einen Hash verwenden, der nur einen Wert für jeden Schlüssel enthält:

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

Ich mag die Antworten von jmah und Head. Aber bewahren sie die Array-Reihenfolge? Sie könnten in späteren Versionen von Ruby, da in der Sprachspezifikation einige Anforderungen zum Beibehalten der Hash-Einfügereihenfolge enthalten sind, verwendet werden.

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

ActiveSupport-Implementierung:

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

Wenn Sie nun die Attributwerte sortieren können, können Sie dies tun:

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

Das ist für ein 1-Attribut einzigartig, aber das gleiche kann mit einer lexikografischen Sortierung durchgeführt werden ...

0
Purfideas