Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uniq by object attribute in Ruby

Use Array#uniq with a block:

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

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 << t
      should_keep
    end
  end
end

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

Do it on the database level:

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

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

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

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.


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