137

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

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

4

15 回答 15

219

Array#uniq与块一起使用:

@photos = @photos.uniq { |p| p.album_id }
于 2012-04-10T05:57:30.980 回答
22

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
于 2008-09-22T08:18:32.103 回答
17

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

YourModel.find(:all, :group => "status")
于 2008-09-21T01:42:06.527 回答
15

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

@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
于 2016-06-08T14:21:52.000 回答
6

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

[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

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

于 2008-09-20T23:45:15.123 回答
5

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

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

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

h = {}
objs.each {|e| h[e.attr]=e}
h.values
于 2008-10-23T21:11:26.460 回答
4

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

enumerable_collection.uniq(&:property)

……它读起来也更好!

于 2017-09-20T22:55:33.097 回答
3

如果我正确理解了您的问题,我已经使用比较 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
于 2008-09-20T23:59:26.243 回答
3

Array#uniq与块一起使用:

objects.uniq {|obj| obj.attribute}

或者更简洁的方法:

objects.uniq(&:attribute)
于 2019-09-20T18:57:45.493 回答
2

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

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
于 2008-09-21T01:48:53.047 回答
2

Rails 也有一个#uniq_by方法。

参考:参数化数组#uniq(即uniq_by)

于 2011-05-10T05:03:10.060 回答
1

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

h = Set.new
objs.select{|el| h.add?(el.attr)}
于 2012-03-11T16:15:53.887 回答
1

ActiveSupport 实施:

def uniq_by
  hash, array = {}, []
  each { |i| hash[yield(i)] ||= (array << i) }
  array
end
于 2012-07-18T22:31:34.960 回答
0

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

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

于 2008-09-21T01:11:18.113 回答
0

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

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

请注意,在自定义对象的情况下,我们需要覆盖eql?hash方法

于 2021-08-27T07:37:49.063 回答