在Ruby中按对象属性去重

142

什么是选择具有一个或多个属性独特对象的最优雅的方法?

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

15个回答

226

使用带块的 Array#uniq 方法:

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

5
这是针对ruby 1.9和更高版本的正确答案。 - nurettin
3
对于早期版本的Ruby,您可以始终使用 require 'backports' :-) - Marc-André Lafortune
哈希方法更好,如果你想按专辑ID分组,同时(例如)总结num_plays。 - thekingoftruth
21
你可以使用to_proc来改进它(http://www.ruby-doc.org/core-1.9.3/Symbol.html#method-i-to_proc):`@photos.uniq &:album_id`。 - joaomilho
@brauliobo 对于 Ruby 1.8 版本,您需要在同一 SO 页面下方阅读以下内容: https://dev59.com/gnVD5IYBdhLWcg3wDG_l#113770 - Peter H. Boling
以不同的方式,@photos = @photos.uniq(&:album_id) - Ahsan

22
在您的项目中添加uniq_by方法到数组中。它类似于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

1
不错的API,但对于大型数组来说,它的扩展性表现很差(看起来像O(n^2))。通过将转换成哈希集来解决这个问题。 - tribalvibes
7
这个答案过时了。Ruby版本 >= 1.9 已经有带有块的 Array#uniq方法,可以精确地执行这个操作,就像被接受的答案中所述一样。 - Peter H. Boling

17

在数据库层面上进行操作:

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

1
如果有多个字段呢,出于好奇? - Ryan Bigg

16
你可以使用这个技巧从数组中选择具有多个属性的唯一元素:
@photos = @photos.uniq { |p| [p.album_id, p.author_id] }

如此明显,如此 Ruby。这只是祝福 Ruby 的另一个理由。 - ToTenMilan

6

我最初建议在数组上使用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使用哈希来实现唯一性。以下是另外几种实现方式:

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

这是一个不错的一行代码,但我怀疑这可能会更快:

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

5

使用带有块的Array#uniq方法:

objects.uniq {|obj| obj.attribute}

或者更简洁的方法:
objects.uniq(&:attribute)

4
我发现最优雅的方法是使用一个带有块的 Array#uniq

enumerable_collection.uniq(&:property)

它看起来更易读了!


3
如果我正确理解你的问题,我曾经使用一种类似于hacky的方法来解决这个问题,即比较Marshal对象以确定是否有任何属性不同。以下代码末尾的inject是一个例子:
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

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接