通过Ruby中的对象属性Uniq

什么是最优雅的方式来select对于一个或多个属性唯一的数组中的对象?

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

在块中使用Array#uniq

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

uniq_by方法添加到项目中的Array。 它通过类比sort_by 。 所以uniq_byuniq因为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)。 更好的是像(未经testing)…

 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 

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

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

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

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} [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} [1, 2, 3, 4, 5, 6, 7].detect{|e| e>3}给我们4

不过,我不确定你要去哪里。

我喜欢jmah使用哈希执行唯一性。 这里有几个更多的方式来皮肤猫:

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

这是一个很好的1class轮,但我怀疑这可能会快一点:

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

如果我正确理解你的问题,我已经使用准哈克法比较了封送对象来确定是否有任何属性变化,从而解决了这个问题。 在下面的代码结尾注入将是一个例子:

 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 

你可以使用这个技巧来从数组中select一些独立的属性元素:

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

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

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

Rails也有一个#uniq_by方法 – 参见参数化数组#uniq(即,uniq_by)

我喜欢jmah和Head的答案。 但是他们保存数组顺序? 他们可能在更高版本的ruby,因为有一些哈希插入顺序保留要求写入语言规范,但是这里有一个类似的解决scheme,我喜欢使用保留顺序不pipe。

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

ActiveSupport实现:

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

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

 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 /字典sorting…

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

 enumerable_collection.uniq(&:property) 

它也读得更好!