根据项的属性将对象数组分组构建哈希

20
我想知道在Ruby 1.9中是否有一种更加标准的方法来做到这一点。
我有一个数组,其中包含许多对象,我想使用数组中每个对象的一个属性将它们分组为一个哈希表。
一个非常简单的例子:
> sh = {}
 => {} 
> aers = %w(a b c d ab bc de abc)
 => ["a", "b", "c", "d", "ab", "bc", "de", "abc"] 
> aers.each do |aer|
>     sh[aer.size] = [] if sh[aer.size].nil?
>     sh[aer.size] << aer
>   end
=> ["a", "b", "c", "d", "ab", "bc", "de", "abc"] 
> sh
 => {1=>["a", "b", "c", "d"], 2=>["ab", "bc", "de"], 3=>["abc"]} 
我尝试了这个方法,但是它的输出结果是错误的(如您所见):
 sh = Hash.new([])
 => {} 
> aers.each do |aer|
>     sh[aer.size] << aer
>   end
 => ["a", "b", "c", "d", "ab", "bc", "de", "abc"] 
> sh
 => {} 

2
你的代码不能如预期般正常工作的原因在这里解释了:https://dev59.com/lHE85IYBdhLWcg3wmEtW 和 https://dev59.com/ZnE85IYBdhLWcg3w6n90(这是Ruby中相当普遍的陷阱)。 - Mladen Jablanović
3个回答

43

Ruby已经预见到了你的需求,并使用Enumerable#group_by来满足你的需求:

irb(main):001:0> aers = %w(a b c d ab bc de abc)
#=> ["a", "b", "c", "d", "ab", "bc", "de", "abc"]

irb(main):002:0> aers.group_by{ |s| s.size }
#=> {1=>["a", "b", "c", "d"], 2=>["ab", "bc", "de"], 3=>["abc"]}

在 Ruby 1.9 中,你甚至可以更短地实现这个功能:

irb(main):003:0> aers.group_by(&:size)
#=> {1=>["a", "b", "c", "d"], 2=>["ab", "bc", "de"], 3=>["abc"]}

3
这就是为什么 Ruby 如此酷;它甚至在我们意识到之前就知道了我们想要什么。 - the Tin Man
请注意,在1.8版本中,使用symbol to proc比直接使用block要慢得多。可以参考这个链接:http://confreaks.net/videos/427-rubyconf2010-zomg-why-is-this-code-so-slow ,从31:18处开始观看。但在1.9版本中,它们的性能差别不大。 - Claudio Acciaresi

3

Phrogz是正确的,group_by方法可以使用。你的代码包含了Ruby中的一个陷阱。

aers = %w(a b c d ab bc de abc)
sh = Hash.new([]) # returns the _same_ array everytime the key is not found. 
# sh = Hash.new{|h,v| h[v] = []}  # This one works
p sh, sh.default

aers.each do |aer|
  sh[aer.size] << aer  #modifies the default [] every time
end
p sh, sh.default
p sh[5]

输出

{}
[]
{}
["a", "b", "c", "d", "ab", "bc", "de", "abc"]
["a", "b", "c", "d", "ab", "bc", "de", "abc"]

1
你也可以通过链接方法来实现这一点... 对于这个问题可能只是学术上的兴趣,但仍然是一种值得熟悉的好技巧。
irb(main):017:0> sh = {}
=> {}
irb(main):018:0> aers.collect{|k| k.size}.uniq!.each{|k| sh[k] = aers.select{|j| j.size == k}}
=> [1, 2, 3]
irb(main):019:0> sh
=> {1=>["a", "b", "c", "d"], 2=>["ab", "bc", "de"], 3=>["abc"]}
irb(main):020:0> 

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