【问题标题】:Uniq by object attribute in RubyRuby中对象属性的唯一性
【发布时间】:2010-09-11 16:58:11
【问题描述】:

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

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

【问题讨论】:

    标签: ruby-on-rails ruby arrays unique


    【解决方案1】:

    如果你没有和数组结婚,我们也可以尝试通过集合消除重复

    set = Set.new
    set << obj1
    set << obj2
    set.inspect
    

    请注意,如果是自定义对象,我们需要覆盖 eql?hash 方法

    【讨论】:

      【解决方案2】:

      Rails 还有一个#uniq_by 方法。

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

      【讨论】:

        【解决方案3】:

        Array#uniq 与块一起使用:

        objects.uniq {|obj| obj.attribute}
        

        或者更简洁的方法:

        objects.uniq(&:attribute)
        

        【讨论】:

          【解决方案4】:

          我发现的最优雅的方式是使用带有块的Array#uniq 的衍生产品

          enumerable_collection.uniq(&:property)
          

          ……它读起来也更好!

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

            • 如此明显,如此 Ruby。另一个祝福 Ruby 的理由
            【解决方案6】:

            Array#uniq 与块一起使用:

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

            【讨论】:

            【解决方案7】:

            ActiveSupport 实施:

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

            【讨论】:

              【解决方案8】:

              我喜欢 jmah 和 Head 的回答。但是它们是否保留数组顺序?它们可能会出现在更高版本的 ruby​​ 中,因为在语言规范中写入了一些保留哈希插入顺序的要求,但这里有一个类似的解决方案,我喜欢使用它无论如何都保留顺序。

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

              【讨论】:

                【解决方案9】:

                uniq_by 方法添加到项目中的Array。它与sort_by 类比工作。所以uniq_by 对应于uniq,就像sort_by 对应于sort。用法:

                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
                

                【讨论】:

                • 不错的 api,但是对于大型数组来说,它的扩展性能很差(看起来像 O(n^2))。可以通过将转换设置为哈希集来修复。
                • 这个答案已经过时了。 Ruby >= 1.9 具有 Array#uniq 和一个块,正如在接受的答案中那样。
                【解决方案10】:

                我喜欢 jmah 使用哈希来强制唯一性。这里还有几种给猫剥皮的方法:

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

                这是一个不错的 1-liner,但我怀疑这可能会快一点:

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

                【讨论】:

                  【解决方案11】:

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

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

                  【讨论】:

                    【解决方案12】:

                    在数据库级别进行:

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

                    【讨论】:

                    • 如果出于兴趣而涉及多个领域怎么办?
                    【解决方案13】:

                    现在,如果您可以对属性值进行排序,则可以这样做:

                    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 属性唯一的,但同样的事情可以用字典排序来完成......

                    【讨论】:

                      【解决方案14】:

                      如果我正确理解了您的问题,我已经使用 quasi-hacky 方法解决了这个问题,该方法比较了 Marshaled 对象以确定是否有任何属性发生变化。以下代码末尾的注入就是一个例子:

                      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
                      

                      【讨论】:

                        【解决方案15】:

                        我最初建议在 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&gt;3} 给我们4

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

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2011-02-07
                          • 1970-01-01
                          • 2012-11-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2013-06-25
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多