如何合并哈希数组以得到哈希值的数组

这与在Ruby中将一列数组变成一个哈希数组是相反的。

优雅地和/或有效地将散列数组转换为散列,其中值是所有值的数组:

hs = [ { a:1, b:2 }, { a:3, c:4 }, { b:5, d:6 } ] collect_values( hs ) #=> { :a=>[1,3], :b=>[2,5], :c=>[4], :d=>[6] } 

这个简洁的代码几乎可以工作,但在没有重复的情况下无法创build数组:

 def collect_values( hashes ) hashes.inject({}){ |a,b| a.merge(b){ |_,x,y| [*x,*y] } } end collect_values( hs ) #=> { :a=>[1,3], :b=>[2,5], :c=>4, :d=>6 } 

这个代码有效,但是你能写一个更好的版本吗?

 def collect_values( hashes ) # Requires Ruby 1.8.7+ for Object#tap Hash.new{ |h,k| h[k]=[] }.tap do |result| hashes.each{ |h| h.each{ |k,v| result[k]<<v } } end end 

只能在Ruby 1.9中工作的解决scheme是可以接受的,但应该注意这一点。


下面是使用三个不同的散列数组对基础下面的各种答案(以及我自己的几个答案)的结果:

  • 其中每个散列有不同的键,所以不会发生合并:
    [{:a=>1}, {:b=>2}, {:c=>3}, {:d=>4}, {:e=>5}, {:f=>6}, {:g=>7}, ...]

  • 每个哈希都有相同的密钥,所以最大的合并发生:
    [{:a=>1}, {:a=>2}, {:a=>3}, {:a=>4}, {:a=>5}, {:a=>6}, {:a=>7}, ...]

  • 和一个独特和共享密钥的组合:
    [{:c=>1}, {:d=>1}, {:c=>2}, {:f=>1}, {:c=>1, :d=>1}, {:h=>1}, {:c=>3}, ...]
               用户系统总数真实
 Phrogz 2a 0.577000 0.000000 0.577000(0.576000)
 Phrogz 2b 0.624000 0.000000 0.624000(0.620000)
格伦1 0.640000 0.000000 0.640000(0.641000)
 Phrogz 1 0.671000 0.000000 0.671000(0.668000)
迈克尔1 0.702000 0.000000 0.702000(0.700000)
迈克尔2 0.717000 0.000000 0.717000(0.726000)
格伦2 0.765000 0.000000 0.765000(0.764000)
 fl00r 0.827000 0.000000 0.827000(0.836000)
 sawa 0.874000 0.000000 0.874000(0.868000)
 Tokland 1 0.873000 0.000000 0.873000(0.876000)
 Tokland 2 1.077000 0.000000 1.077000(1.073000)
 Phrogz 3 2.106000 0.093000 2.199000(2.209000)

最快的代码是我添加的这个方法:

 def collect_values(hashes) {}.tap{ |r| hashes.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } } end 

我接受了“ 格伦麦克唐纳的回答 ”,因为它在速度方面具有竞争力,相当简洁,但是(最重要的)是因为它指出了使用哈希以自修改默认处理方式来构build方便的危险,因为这可能当用户稍后编制索引时会引入不良的更改。

最后,这里是基准代码,以防您想要自行比较:

 require 'prime' # To generate the third hash require 'facets' # For tokland1's map_by AZSYMBOLS = (:a..:z).to_a TESTS = { '26 Distinct Hashes' => AZSYMBOLS.zip(1..26).map{|a| Hash[*a] }, '26 Same-Key Hashes' => ([:a]*26).zip(1..26).map{|a| Hash[*a] }, '26 Mixed-Keys Hashes' => (2..27).map do |i| factors = i.prime_division.transpose Hash[AZSYMBOLS.values_at(*factors.first).zip(factors.last)] end } def phrogz1(hashes) Hash.new{ |h,k| h[k]=[] }.tap do |result| hashes.each{ |h| h.each{ |k,v| result[k]<<v } } end end def phrogz2a(hashes) {}.tap{ |r| hashes.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } } end def phrogz2b(hashes) hashes.each_with_object({}){ |h,r| h.each{ |k,v| (r[k]||=[]) << v } } end def phrogz3(hashes) result = hashes.inject({}){ |a,b| a.merge(b){ |_,x,y| [*x,*y] } } result.each{ |k,v| result[k] = [v] unless v.is_a? Array } end def glenn1(hs) hs.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h} end def glenn2(hs) hs.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h} end def fl00r(hs) h = Hash.new{|h,k| h[k]=[]} hs.map(&:to_a).flatten(1).each{|v| h[v[0]] << v[1]} h end def sawa(a) a.map(&:to_a).flatten(1).group_by{|k,v| k}.each_value{|v| v.map!{|k,v| v}} end def michael1(hashes) h = Hash.new{|h,k| h[k]=[]} hashes.each_with_object(h) do |h, result| h.each{ |k, v| result[k] << v } end end def michael2(hashes) h = Hash.new{|h,k| h[k]=[]} hashes.inject(h) do |result, h| h.each{ |k, v| result[k] << v } result end end def tokland1(hs) hs.map(&:to_a).flatten(1).map_by{ |k, v| [k, v] } end def tokland2(hs) Hash[hs.map(&:to_a).flatten(1).group_by(&:first).map{ |k, vs| [k, vs.map{|o|o[1]}] }] end require 'benchmark' N = 10_000 Benchmark.bm do |x| x.report('Phrogz 2a'){ TESTS.each{ |n,h| N.times{ phrogz2a(h) } } } x.report('Phrogz 2b'){ TESTS.each{ |n,h| N.times{ phrogz2b(h) } } } x.report('Glenn 1 '){ TESTS.each{ |n,h| N.times{ glenn1(h) } } } x.report('Phrogz 1 '){ TESTS.each{ |n,h| N.times{ phrogz1(h) } } } x.report('Michael 1'){ TESTS.each{ |n,h| N.times{ michael1(h) } } } x.report('Michael 2'){ TESTS.each{ |n,h| N.times{ michael2(h) } } } x.report('Glenn 2 '){ TESTS.each{ |n,h| N.times{ glenn2(h) } } } x.report('fl00r '){ TESTS.each{ |n,h| N.times{ fl00r(h) } } } x.report('sawa '){ TESTS.each{ |n,h| N.times{ sawa(h) } } } x.report('Tokland 1'){ TESTS.each{ |n,h| N.times{ tokland1(h) } } } x.report('Tokland 2'){ TESTS.each{ |n,h| N.times{ tokland2(h) } } } x.report('Phrogz 3 '){ TESTS.each{ |n,h| N.times{ phrogz3(h) } } } end 

拿你的select:

 hs.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h} hs.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h} 

我坚决反对哈希的默认值,因为其他的build议,因为然后检查一个值修改哈希,这似乎是非常错误的我。

 h = Hash.new{|h,k| h[k]=[]} hs.map(&:to_a).flatten(1).each{|v| h[v[0]] << v[1]} 

这个怎么样?

 def collect_values(hashes) h = Hash.new{|h,k| h[k]=[]} hashes.each_with_object(h) do |h, result| h.each{ |k, v| result[k] << v } end end 

编辑 – 也可以与注入,但恕我直言不如:

 def collect_values( hashes ) h = Hash.new{|h,k| h[k]=[]} hashes.inject(h) do |result, h| h.each{ |k, v| result[k] << v } result end end 

与使用map(&:to_a).flatten(1)其他答案一样。 问题是如何修改哈希的值。 我使用了数组是可变的事实。

 def collect_values a a.map(&:to_a).flatten(1).group_by{|k, v| k}. each_value{|v| v.map!{|k, v| v}} end 

对于这些情况,Facet的Enumerable#map_by派上用场。 这个实现无疑比其他实现更慢,但是模块化和紧凑的代码总是更容易维护:

 require 'facets' hs.flat_map(&:to_a).map_by { |k, v| [k, v] } #=> {:b=>[2, 5], :d=>[6], :c=>[4], :a=>[1, 3] 

我认为比较胜利者可能会比较有趣:

 def phrogz2a(hashes) {}.tap{ |r| hashes.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } } end 

略有变化:

 def phrogz2ai(hashes) Hash.new {|h,k| h[k]=[]}.tap {|r| hashes.each {|h| h.each {|k,v| r[k] << v}}} end 

因为可以经常使用这两种方法(通常是创build一个空数组或哈希)。

使用Phrogz的基准代码,这里是他们如何比较:

  user system total real Phrogz 2a 0.440000 0.010000 0.450000 ( 0.444435) Phrogz 2ai 0.580000 0.010000 0.590000 ( 0.580248) 

这个如何?

 hs.reduce({}, :merge) 

最短! 但performance相当糟糕:

  user system total real Phrogz 2a 0.240000 0.010000 0.250000 ( 0.247337) Phrogz 2b 0.280000 0.000000 0.280000 ( 0.274985) Glenn 1 0.290000 0.000000 0.290000 ( 0.290370) Phrogz 1 0.310000 0.000000 0.310000 ( 0.315548) Michael 1 0.360000 0.000000 0.360000 ( 0.356760) Michael 2 0.360000 0.000000 0.360000 ( 0.360119) Glenn 2 0.370000 0.000000 0.370000 ( 0.369354) fl00r 0.390000 0.000000 0.390000 ( 0.385883) sawa 0.410000 0.000000 0.410000 ( 0.408190) Tokland 1 0.410000 0.000000 0.410000 ( 0.410097) Tokland 2 0.490000 0.000000 0.490000 ( 0.497325) Ich 1.410000 0.000000 1.410000 ( 1.413176) # <<-- new Phrogz 3 1.760000 0.010000 1.770000 ( 1.762979) 
 [{'a' => 1}, {'b' => 2}, {'c' => 3}].reduce Hash.new, :merge