数组哈希的所有组合

摘要
给定Hash,其中一些值是数组,如何获得所有可能组合的哈希数组?

测试用例

options = { a:[1,2], b:[3,4], c:5 } p options.self_product #=> [{:a=>1, :b=>3, :c=>5}, #=> {:a=>1, :b=>4, :c=>5}, #=> {:a=>2, :b=>3, :c=>5}, #=> {:a=>2, :b=>4, :c=>5}] 

当特定键的值不是数组时,它应该只是按原样包含在每个生成的哈希中,就像它包装在数组中一样。

动机
我需要为不同的选项生成给定各种值的测试数据。 虽然我可以使用[1,2].product([3,4],[5])来获取所有可能值的笛卡尔积,但我宁愿使用散列来标记我的输入和输出,以便代码比仅使用数组索引更容易解释。

我建议进行一些预处理以保持结果的一般性:

 options = { a:[1,2], b:[3,4], c:5 } options.each_key {|k| options[k] = [options[k]] unless options[k].is_a? Array} => {:a=>[1, 2], :b=>[3, 4], :c=>[5]} 

我编辑了一些改进,主要是使用inject({})

 class Hash def self_product f, *r = map {|k,v| [k].product(v).map {|e| Hash[*e]}} f.product(*r).map {|a| a.inject({}) {|h,e| e.each {|k,v| h[k]=v}; h}} end end 

…虽然我更喜欢@ Phrogz的’第二次尝试’,通过预处理5=>[5] ,它将是:

 class Hash def self_product f, *r = map {|k,v| [k].product(v)} f.product(*r).map {|a| Hash[*a.flatten]} end end 

第一次尝试:

 class Hash #=> Given a hash of arrays get an array of hashes #=> For example, `{ a:[1,2], b:[3,4], c:5 }.self_product` yields #=> [ {a:1,b:3,c:5}, {a:1,b:4,c:5}, {a:2,b:3,c:5}, {a:2,b:4,c:5} ] def self_product # Convert array values into single key/value hashes all = map{|k,v| [k].product(v.is_a?(Array) ? v : [v]).map{|k,v| {k=>v} }} #=> [[{:a=>1}, {:a=>2}], [{:b=>3}, {:b=>4}], [{:c=>5}]] # Create the product of all mini hashes, and merge them into a single hash all.first.product(*all[1..-1]).map{ |a| a.inject(&:merge) } end end p({ a:[1,2], b:[3,4], c:5 }.self_product) #=> [{:a=>1, :b=>3, :c=>5}, #=> {:a=>1, :b=>4, :c=>5}, #=> {:a=>2, :b=>3, :c=>5}, #=> {:a=>2, :b=>4, :c=>5}] 

第二次尝试,受@ Cary的回答启发:

 class Hash def self_product first, *rest = map{ |k,v| [k].product(v.is_a?(Array) ? v : [v]) } first.product(*rest).map{ |x| Hash[x] } end end 

除了更优雅之外,第二个答案也比创建大结果时的第一个答案快4.5倍(262k哈希,每个6键):

 require 'benchmark' Benchmark.bm do |x| n = *1..8 h = { a:n, b:n, c:n, d:n, e:n, f:n } %w[phrogz1 phrogz2].each{ |n| x.report(n){ h.send(n) } } end #=> user system total real #=> phrogz1 4.450000 0.050000 4.500000 ( 4.502511) #=> phrogz2 0.940000 0.050000 0.990000 ( 0.980424)