Uniq by object attribute in Ruby

匿名 (未验证) 提交于 2019-12-03 02:15:02

问题:

What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?

These objects are stored in ActiveRecord so using AR's methods would be fine too.

回答1:

Use Array#uniq with a block:

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


回答2:

Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:

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

The implementation:

class Array   def uniq_by(&blk)     transforms = []     self.select do |el|       should_keep = !transforms.include?(t=blk[el])       transforms 

Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.

EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...

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 


回答3:

Do it on the database level:

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


回答4:

I had originally suggested using the select method on Array. To wit:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} gives us [2,4,6] back.

But if you want the first such object, use detect.

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

I'm not sure what you're going for here, though.



回答5:

I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:

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

That's a nice 1-liner, but I suspect this might be a little faster:

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


回答6:

You can use this trick to select unique by several attributes elements from array:

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


回答7:

If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:

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 


回答8:

You can use a hash, which contains only one value for each key:

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


回答9:

Rails also has a #uniq_by method - see Parameterized Array#uniq (i.e., uniq_by)



回答10:

I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.

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


回答11:

ActiveSupport implementation:

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


回答12:

Now if you can sort on the attribute values this can be done:

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 

That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...



回答13:

The most elegant way I have found is a spin-off using Array#uniq with a block

enumerable_collection.uniq(&:property) 

…it reads better too!



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!