Ruby中的Uniq按对象属性

时间:2020-03-06 14:29:46  来源:igfitidea点击:

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

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

解决方案

我最初建议在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

不过,我不确定我们要在这里做什么。

如果我正确理解了问题,那么我将通过比较编组对象以确定是否任何属性发生变化的准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

现在,如果我们可以对属性值进行排序,则可以完成以下操作:

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属性的唯一标记,但是可以通过词典顺序完成相同的操作...

在数据库级别上执行此操作:

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

我们可以使用哈希,每个哈希仅包含一个值:

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

在项目中将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

我喜欢jmah使用哈希来强制唯一性。这里有几种其他方法可以给猫咪蒙皮:

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

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

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