Ruby中的Uniq按对象属性


126

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

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

Answers:


200

使用Array#uniq带块:

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

5
这是ruby 1.9和更高版本的正确答案。
nurettin

2
+1。而对于早期的红宝石,总有require 'backports':-)
马克-安德烈·Lafortune

如果要在总结num_plays的同时说出album_id进行分组,则哈希方法会更好。
thekingoftruth 2013年

20
您可以使用to_proc(ruby-doc.org/core-1.9.3/Symbol.html#method-i-to_proc)进行改进:@photos.uniq &:album_id
joaomilho 2013年

@brauliobo for Ruby 1.8,您需要以相同的方式阅读以下内容:stackoverflow.com/a/113770/213191
Peter H. Boling

22

uniq_by方法添加到项目中的Array中。类似于sort_by。所以,uniq_byuniqsort_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

1
好的api,但是大型数组的缩放性能会很差(看起来像O(n ^ 2))。可以通过转换hashset来解决。
tribalvibes 2010年

7
这个答案已经过时了。Ruby> = 1.9的Array#uniq带有一个可以完全做到这一点的块,如公认的答案所示。
Peter H. Boling 2014年

17

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

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

1
如果不止一个领域而不感兴趣,该怎么办?
瑞安·比格

12

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

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

很明显,所以露比。只是另一个原因保佑红宝石
ToTenMilan

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

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


5

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

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

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

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

3

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

3

我发现的最优雅的方法是使用分Array#uniq块进行分拆

enumerable_collection.uniq(&:property)

…读起来也更好!


2

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

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



1

我喜欢jmah和Head的答案。但是它们保留数组顺序吗?由于在语言规范中写入了一些保留哈希值以保留顺序的要求,因此它们可能在ruby的更高版本中使用,但是我喜欢使用类似的解决方案,该方法可以保留顺序。

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

1

ActiveSupport实施:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.