Uniq menurut atribut objek di Ruby

127

Apa cara paling elegan untuk memilih objek dalam larik yang unik sehubungan dengan satu atau beberapa atribut?

Objek-objek ini disimpan di ActiveRecord jadi menggunakan metode AR juga tidak masalah.

sutee
sumber

Jawaban:

201

Gunakan Array#uniqdengan satu blok:

@photos = @photos.uniq { |p| p.album_id }
Jalur
sumber
5
Ini adalah jawaban yang benar untuk ruby 1.9 dan versi yang lebih baru.
nurettin
2
+1. Dan untuk Ruby sebelumnya, selalu ada require 'backports':-)
Marc-André Lafortune
Metode hash lebih baik jika Anda ingin mengelompokkan dengan mengatakan album_id sementara (katakanlah) menjumlahkan num_plays.
thekingoftruth
20
Anda dapat memperbaikinya dengan to_proc ( ruby-doc.org/core-1.9.3/Symbol.html#method-i-to_proc ):@photos.uniq &:album_id
joaomilho
@brauliobo untuk Ruby 1.8 Anda perlu membaca tepat di bawah ini di SO yang sama ini: stackoverflow.com/a/113770/213191
Peter H.Boling
22

Tambahkan uniq_bymetode ke Array dalam proyek Anda. Ini bekerja dengan analogi dengan sort_by. Begitu uniq_byjuga dengan uniqapa sort_byadanya sort. Pemakaian:

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

Pelaksanaan:

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

Perhatikan bahwa ini mengembalikan larik baru daripada mengubah larik Anda saat ini. Kami belum menulis uniq_by!metode tetapi harus cukup mudah jika Anda mau.

EDIT: Tribalvibes menunjukkan bahwa penerapannya adalah O (n ^ 2). Lebih baik menjadi sesuatu seperti (belum teruji) ...

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
Daniel Lucraft
sumber
1
Api yang bagus tapi itu akan memiliki kinerja penskalaan yang buruk (sepertinya O (n ^ 2)) untuk array besar. Bisa diperbaiki dengan membuat transformasi menjadi hashset.
tribalvibes
7
Jawaban ini sudah ketinggalan zaman. Ruby> = 1.9 memiliki Array # uniq dengan blok yang melakukan hal ini, seperti pada jawaban yang diterima.
Peter H. Boling
17

Lakukan di level database:

YourModel.find(:all, :group => "status")
mislav
sumber
1
dan bagaimana jika itu lebih dari satu bidang, karena minat?
Ryan Bigg
12

Anda dapat menggunakan trik ini untuk memilih unik dengan beberapa elemen atribut dari array:

@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
YauheniNinja
sumber
sangat jelas, jadi Ruby. Hanya alasan lain untuk memberkati Ruby
ToTenMilan
6

Saya awalnya menyarankan menggunakan selectmetode pada Array. Yakni:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} memberi kami [2,4,6]kembali.

Tetapi jika Anda menginginkan objek seperti itu pertama, gunakan detect.

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

Saya tidak yakin apa tujuan Anda di sini.

Alex M
sumber
5

Saya suka penggunaan Hash oleh jmah untuk menegakkan keunikan. Berikut beberapa cara lagi untuk menguliti kucing itu:

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

Itu 1-liner yang bagus, tapi saya rasa ini mungkin sedikit lebih cepat:

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

Jika saya memahami pertanyaan Anda dengan benar, saya telah mengatasi masalah ini menggunakan pendekatan semu-hacky untuk membandingkan objek Marshaled untuk menentukan apakah ada atribut yang bervariasi. Injeksi di akhir kode berikut akan menjadi contoh:

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
Drew Olson
sumber
3

Cara paling elegan yang saya temukan adalah spin-off menggunakan Array#uniqdengan satu blok

enumerable_collection.uniq(&:property)

… Terbaca lebih baik juga!

iGbanam
sumber
2

Anda dapat menggunakan hash, yang hanya berisi satu nilai untuk setiap kunci:

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

Gunakan Array # uniq dengan satu blok:

objects.uniq {|obj| obj.attribute}

Atau pendekatan yang lebih ringkas:

objects.uniq(&:attribute)
Muhamad Najjar
sumber
1

Saya suka jawaban jmah dan Head. Tapi apakah mereka mempertahankan urutan array? Mereka mungkin ada di versi ruby ​​yang lebih baru karena ada beberapa persyaratan penyimpanan urutan penyisipan hash yang ditulis ke dalam spesifikasi bahasa, tetapi berikut adalah solusi serupa yang saya suka gunakan yang mempertahankan ketertiban.

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

Implementasi ActiveSupport:

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

Sekarang jika Anda dapat mengurutkan nilai atribut, ini dapat dilakukan:

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

Itu untuk 1-atribut unik, tetapi hal yang sama dapat dilakukan dengan pengurutan leksikografis ...

Purfideas
sumber