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

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の答えに触発された2回目の試み:

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

よりエレガントであることに加えて、2 番目の回答は、大きな結果 (それぞれ 6 つのキーを持つ 262k ハッシュ) を作成する場合、最初の回答よりも約 4.5 倍高速です。

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 に答える