Uniq by object attribute in Ruby

前端 未结 14 1784
我寻月下人不归
我寻月下人不归 2020-11-30 23:20

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 us

相关标签:
14条回答
  • 2020-11-30 23:49

    Rails also has a #uniq_by method.

    Reference: Parameterized Array#uniq (i.e., uniq_by)

    0 讨论(0)
  • 2020-11-30 23:50

    Use Array#uniq with a block:

    objects.uniq {|obj| obj.attribute}
    

    Or a more concise approach:

    objects.uniq(&:attribute)
    
    0 讨论(0)
  • 2020-11-30 23:51

    Do it on the database level:

    YourModel.find(:all, :group => "status")
    
    0 讨论(0)
  • 2020-11-30 23:54

    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 << a if uniqs.empty? || a.val != uniqs.last.val
      uniqs
    end
    

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

    0 讨论(0)
  • 2020-11-30 23:58

    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)}
    
    0 讨论(0)
  • 2020-11-30 23:59

    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
    
    0 讨论(0)
提交回复
热议问题