Thuộc tính Uniq by object trong Ruby


127

Cách đơn giản nhất để chọn ra các đối tượng trong một mảng là duy nhất đối với một hoặc nhiều thuộc tính là gì?

Các đối tượng này được lưu trữ trong ActiveRecord nên việc sử dụng các phương thức của AR cũng sẽ ổn.

Câu trả lời:


201

Sử dụng Array#uniqvới một khối:

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

5
Đây là câu trả lời chính xác cho ruby 1.9 và các phiên bản mới hơn.
nurettin

2
+1. Và đối với những viên Hồng ngọc trước đó, luôn có require 'backports':-)
Marc-André Lauckyne

Phương thức băm sẽ tốt hơn nếu bạn muốn nhóm theo album_id trong khi (nói) tổng hợp num_plays.
thekingoftruth

20
Bạn có thể cải thiện nó bằng to_proc ( ruby-doc.org/core-1.9.3/Symbol.html#method-i-to_proc ):@photos.uniq &:album_id
joaomilho

@brauliobo cho Ruby 1.8 bạn cần phải đọc ngay dưới trong cùng một SO này: stackoverflow.com/a/113770/213191
Peter H. Boling

22

Thêm uniq_byphương thức vào Array trong dự án của bạn. Nó hoạt động bằng cách tương tự với sort_by. Vì vậy, uniq_byuniqnhư sort_bysort. Sử dụng:

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

Việc thực hiện:

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

Lưu ý rằng nó trả về một mảng mới thay vì sửa đổi mảng hiện tại của bạn tại chỗ. Chúng tôi chưa viết một uniq_by!phương pháp nhưng nó sẽ đủ dễ dàng nếu bạn muốn.

CHỈNH SỬA: Tribalvibes chỉ ra rằng việc triển khai đó là O (n ^ 2). Tốt hơn sẽ là một cái gì đó giống như (chưa được kiểm tra) ...

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 tốt nhưng điều đó sẽ có hiệu suất chia tỷ lệ kém (trông giống như O (n ^ 2)) đối với các mảng lớn. Có thể được khắc phục bằng cách tạo các biến đổi một bộ băm.
tribalvibes

7
Câu trả lời này đã lỗi thời. Ruby> = 1.9 có Array # uniq với một khối thực hiện chính xác điều này, như trong câu trả lời được chấp nhận.
Peter H. Boling

17

Làm điều đó ở cấp độ cơ sở dữ liệu:

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

1
và điều gì sẽ xảy ra nếu nó là nhiều hơn một lĩnh vực, không được quan tâm?
Ryan Bigg 21/09/08

12

Bạn có thể sử dụng thủ thuật này để chọn duy nhất theo một số phần tử thuộc tính từ mảng:

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

quá rõ ràng, vì vậy Ruby. Chỉ là một lý do để ban phước của Ruby
ToTenMilan

6

Ban đầu tôi đã đề xuất sử dụng selectphương thức trên Array. Nói một cách dí dỏm:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} trả lại cho chúng tôi [2,4,6].

Nhưng nếu bạn muốn đối tượng đầu tiên như vậy, hãy sử dụng detect.

[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}cho chúng tôi 4.

Tuy nhiên, tôi không chắc bạn sẽ làm gì ở đây.


5

Tôi thích jmah sử dụng Hash để thực thi tính duy nhất. Dưới đây là một số cách khác để lột da mèo:

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

Đó là một lớp lót tốt, nhưng tôi nghi ngờ điều này có thể nhanh hơn một chút:

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

3

Nếu tôi hiểu câu hỏi của bạn một cách chính xác, tôi đã giải quyết vấn đề này bằng cách sử dụng phương pháp gần như khó hiểu là so sánh các đối tượng Marshaled để xác định xem có bất kỳ thuộc tính nào khác nhau hay không. Việc tiêm vào cuối đoạn mã sau sẽ là một ví dụ:

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

Cách thanh lịch nhất mà tôi đã tìm thấy là một spin-off sử dụng Array#uniqvới một khối

enumerable_collection.uniq(&:property)

… Nó cũng đọc tốt hơn!


2

Bạn có thể sử dụng một hàm băm, chỉ chứa một giá trị cho mỗi khóa:

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

2

Sử dụng Array # uniq với một khối:

objects.uniq {|obj| obj.attribute}

Hoặc một cách tiếp cận ngắn gọn hơn:

objects.uniq(&:attribute)


1

Tôi thích câu trả lời của jmah và Head. Nhưng chúng có bảo toàn thứ tự mảng không? Chúng có thể có trong các phiên bản sau của ruby ​​vì đã có một số yêu cầu bảo toàn thứ tự chèn thêm hàm băm được viết vào đặc tả ngôn ngữ, nhưng đây là một giải pháp tương tự mà tôi muốn sử dụng để bảo toàn thứ tự.

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

1

Triển khai ActiveSupport:

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

0

Bây giờ nếu bạn có thể sắp xếp các giá trị thuộc tính, điều này có thể được thực hiện:

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

Đó là đối với 1 thuộc tính duy nhất, nhưng điều tương tự cũng có thể được thực hiện với sắp xếp từ vựng ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.