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.

Jawaban:


201

Gunakan Array#uniqdengan satu blok:

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

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

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")

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

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.


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

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

3

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

enumerable_collection.uniq(&:property)

… Terbaca lebih baik juga!


2

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

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



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)}

1

Implementasi ActiveSupport:

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

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 ...

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.