Ruby 中的对象属性 Uniq

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

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

65506 次浏览

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

不过我不知道你来这里干什么。

如果我没有理解错您的问题,那么我已经使用类似黑客的方法解决了这个问题,即比较编组对象以确定是否有任何属性发生了变化。下面代码末尾的注入就是一个例子:

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

这是一个单属性的唯一,但同样的事情可以做的 w/字典排序..。

在数据库级别上执行:

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 使用 Hash 来强调独特性:

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

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

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

Rails 还有一个 #uniq_by方法。

参考资料: 参数化数组 # uniq (即 uniq _ by)

我喜欢 jmah 和 Head 的答案。但它们能保持数组顺序吗?它们可能会出现在 Ruby 的后续版本中,因为语言规范中已经写入了一些保持散列插入顺序的要求,但是这里有一个类似的解决方案,我喜欢使用它来保持顺序。

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

与块一起使用 Array#uniq:

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

ActiveSupport 实现:

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

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

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

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

enumerable_collection.uniq(&:property)

读起来也更好!

对块使用 Array # uniq:

objects.uniq {|obj| obj.attribute}

或者一个更简洁的方法:

objects.uniq(&:attribute)

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

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

注意,在定制对象的情况下,我们需要重写 eql?hash方法