Câu trả lời:
Sử dụng Array#uniq
với một khối:
@photos = @photos.uniq { |p| p.album_id }
require 'backports'
:-)
@photos.uniq &:album_id
Thêm uniq_by
phươ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_by
là uniq
như sort_by
là sort
. 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
Làm điều đó ở cấp độ cơ sở dữ liệu:
YourModel.find(:all, :group => "status")
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] }
Ban đầu tôi đã đề xuất sử dụng select
phươ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.
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
Cách thanh lịch nhất mà tôi đã tìm thấy là một spin-off sử dụng Array#uniq
với một khối
enumerable_collection.uniq(&:property)
… Nó cũng đọc tốt hơn!
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)
Rails cũng có một #uniq_by
phương pháp.
Tham chiếu: Parameterized Array # uniq (tức là uniq_by)
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)}
Triển khai ActiveSupport:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
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 ...