【发布时间】:2011-07-26 08:17:43
【问题描述】:
这与Turning a Hash of Arrays into an Array of Hashes in 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] }
这段简洁的代码几乎可以工作,但是当没有重复时无法创建数组:
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 的解决方案是可以接受的,但应注意。
以下是使用三个不同的哈希数组对以下各种答案(以及我自己的一些答案)进行基准测试的结果:
每个哈希都有不同的键,因此不会发生合并:
[{: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}, ...]
最快的代码是我加的这个方法:
def collect_values(hashes)
{}.tap{ |r| hashes.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } }
end
我接受了“glenn mcdonald's answer”,因为它在速度方面具有竞争力,相当简洁,但(最重要的是)因为它指出了使用带有自修改默认 proc 的哈希以方便构建的危险,因为这可能会在用户稍后对其编制索引时引入错误的更改。
最后,这里是基准代码,如果您想运行自己的比较:
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
【问题讨论】:
-
如果您需要此功能,请随时为这个问题投票,以便其他人可以找到它。我问了这个问题并包含了一些工作代码,因为(据我所知)在 Stack Overflow 上已经没有很好的答案了。
-
+1 提出一个有趣的问题,唉,到目前为止,我想不出比您的工作解决方案更好的方法。
-
我认为每个答案也应该根据您的
hs提供标准化的基准测试结果。 -
@theTinMan 问,你会收到:)