2

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

测试用例

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])用来获取所有可能值的笛卡尔积,但我宁愿使用散列来标记我的输入和输出,这样代码比仅使用数组索引更容易解释。

4

2 回答 2

2

我建议进行一些预处理以保持结果通用:

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 的“第二次尝试”,但在 pre-processing5=>[5]中,它将是:

class Hash
  def self_product
    f, *r = map {|k,v| [k].product(v)}
    f.product(*r).map {|a| Hash[*a.flatten]}
  end
end
于 2013-09-30T23:31:19.650 回答
1

第一次尝试:

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)
于 2013-09-30T17:46:04.083 回答