首页 文章

Uniq by Ruby中的object属性

提问于
浏览
107

选择数组中对于一个或多个属性唯一的对象的最优雅方法是什么?

这些对象存储在ActiveRecord中,因此使用AR的方法也可以 .

13 回答

  • 5

    我找到的最优雅的方法是使用带有块的Array#uniq进行分拆

    enumerable_collection.uniq(&:property)
    

    ......它也读得更好!

  • 171

    我喜欢jmah和Head的答案 . 但他们保留阵列顺序吗?它们可能在更新版本的ruby中,因为已经有一些写入语言规范的哈希插入顺序保留要求,但这里是一个类似的解决方案,我喜欢使用它保留顺序,无论如何 .

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

    ActiveSupport实现:

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

    现在,如果您可以对属性值进行排序,则可以执行以下操作:

    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
    

    这是一个1属性的唯一,但同样的事情可以用词典排序...

  • 0

    Rails也有一个#uniq_by方法 - 见Parameterized Array#uniq (i.e., uniq_by)

  • 2

    使用带有块的Array#uniq

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

    uniq_by 方法添加到项目中的Array . 它与 sort_by 类似 . 所以 uniq_byuniq ,因为 sort_bysort . 用法:

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

    实施:

    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
    

    请注意,它返回一个新数组,而不是修改当前的数组 . 我们还没有编写 uniq_by! 方法,但如果你愿意,它应该很容易 .

    编辑:Tribalvibes指出该实现是O(n ^ 2) . 更好的是(未经测试)......

    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
    
  • 14

    在数据库级别上执行:

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

    我最初建议在Array上使用 select 方法 . 以机智:

    [1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} 给了我们 [2,4,6] 回来了 .

    但是如果你想要第一个这样的对象,请使用 detect .

    [1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} 给了我们 4 .

    不过,我不确定你的目的是什么 .

  • 5

    我喜欢jmah使用Hash来强制执行唯一性 . 以下是另外两种皮肤猫的方法:

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

    这是一个很好的1-liner,但我怀疑这可能会更快一点:

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

    您可以使用此技巧从数组中的几个属性元素中选择唯一:

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

    如果我正确理解你的问题,我已经使用比较Marshaled对象的准hacky方法解决了这个问题,以确定是否有任何属性变化 . 以下代码末尾的注入将是一个示例:

    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
    
  • 1

    您可以使用散列,每个键只包含一个值:

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

相关问题