All possible combinations from a hash of arrays in

2019-03-14 16:24发布

问题:

What I have:

Let's say I have a hash like this, with various values belonging to one parameter.

a = {}
a[:bitrate] = ["100", "500", "1000"]
a[:fps] = ["15", "30"]
a[:qp] = ["20", "30"]

What I need:

I need some way to iteratively get all the possible combinations of these values, so, with all the parameter/value pairs:

  • bitrate = 100, fps = 15, qp = 20
  • bitrate = 500, fps = 15, qp = 30
  • ...

The number of parameters (i.e. the keys) and the number of values (i.e. the length of the value arrays) are not known beforehand. Ideally, I'd do something like:

a.foo do |ret|
  puts ret.keys   # => ["bitrate", "fps", "qp"]
  puts ret.values # => ["100", "15", "20"]
end

… where the block is called for each possible combination. How can I define foo?


What I (probably) don't need:

Now, I know this: Combine array of array into all possible combinations, forward only, in Ruby, suggesting something like:

a.first.product(*a[1..-1]).map(&:join)

But this operates on values and arrays in arrays only, and I need the original reference to the parameter's name.

回答1:

a = {}
a[:bitrate] = ["100", "500", "1000"]
a[:fps] = ["15", "30"]
a[:qp] = ["20", "30"]

def product_hash(hsh)
  attrs   = hsh.values
  keys    = hsh.keys
  product = attrs[0].product(*attrs[1..-1])
  product.map{ |p| Hash[keys.zip p] }
end

product_hash(a)

you'll get

[{:bitrate=>"100", :fps=>"15", :qp=>"20"},
 {:bitrate=>"100", :fps=>"15", :qp=>"30"},
 {:bitrate=>"100", :fps=>"30", :qp=>"20"},
 {:bitrate=>"100", :fps=>"30", :qp=>"30"},
 {:bitrate=>"500", :fps=>"15", :qp=>"20"},
 {:bitrate=>"500", :fps=>"15", :qp=>"30"},
 {:bitrate=>"500", :fps=>"30", :qp=>"20"},
 {:bitrate=>"500", :fps=>"30", :qp=>"30"},
 {:bitrate=>"1000", :fps=>"15", :qp=>"20"},
 {:bitrate=>"1000", :fps=>"15", :qp=>"30"},
 {:bitrate=>"1000", :fps=>"30", :qp=>"20"},
 {:bitrate=>"1000", :fps=>"30", :qp=>"30"}]

You can also add new key to your hash.

a = {}
a[:bitrate] = ["100", "500", "1000"]
a[:fps] = ["15", "30"]
a[:qp] = ["20", "30"]
a[:bw] = [true, false]

product_hash(a)

#=>
[{:bitrate=>"100", :fps=>"15", :qp=>"20", :bw=>true},
 {:bitrate=>"100", :fps=>"15", :qp=>"20", :bw=>false},
 {:bitrate=>"100", :fps=>"15", :qp=>"30", :bw=>true},
 {:bitrate=>"100", :fps=>"15", :qp=>"30", :bw=>false},
 {:bitrate=>"100", :fps=>"30", :qp=>"20", :bw=>true},
 {:bitrate=>"100", :fps=>"30", :qp=>"20", :bw=>false},
 {:bitrate=>"100", :fps=>"30", :qp=>"30", :bw=>true},
 {:bitrate=>"100", :fps=>"30", :qp=>"30", :bw=>false},
 {:bitrate=>"500", :fps=>"15", :qp=>"20", :bw=>true},
 {:bitrate=>"500", :fps=>"15", :qp=>"20", :bw=>false},
 {:bitrate=>"500", :fps=>"15", :qp=>"30", :bw=>true},
 {:bitrate=>"500", :fps=>"15", :qp=>"30", :bw=>false},
 {:bitrate=>"500", :fps=>"30", :qp=>"20", :bw=>true},
 {:bitrate=>"500", :fps=>"30", :qp=>"20", :bw=>false},
 {:bitrate=>"500", :fps=>"30", :qp=>"30", :bw=>true},
 {:bitrate=>"500", :fps=>"30", :qp=>"30", :bw=>false},
 {:bitrate=>"1000", :fps=>"15", :qp=>"20", :bw=>true},
 {:bitrate=>"1000", :fps=>"15", :qp=>"20", :bw=>false},
 {:bitrate=>"1000", :fps=>"15", :qp=>"30", :bw=>true},
 {:bitrate=>"1000", :fps=>"15", :qp=>"30", :bw=>false},
 {:bitrate=>"1000", :fps=>"30", :qp=>"20", :bw=>true},
 {:bitrate=>"1000", :fps=>"30", :qp=>"20", :bw=>false},
 {:bitrate=>"1000", :fps=>"30", :qp=>"30", :bw=>true},
 {:bitrate=>"1000", :fps=>"30", :qp=>"30", :bw=>false}]


回答2:

Just FYI I took fl00r's approach and monkey-patched it. I like it a bit better.

class Hash
  def product
    product = values[0].product(*values[1..-1])
    product.map{|p| Hash[keys.zip p]}
  end
end


回答3:

I believe fl00r's answer is almost perfect but has a drawback. It assumes that hsh.values and hsh.keys will have a matching order, which as far as I know, is not warrantied. So you will probably need an extra step to ensure that. Maybe something like:

def product_hash(hsh)
  keys  = hsh.keys
  attrs = keys.map { |key| hsh[key] }
  product = attrs[0].product(*attrs[1..-1])
  product.map{ |p| Hash[keys.zip p] }
end

But fl00r can correct me if I'm wrong.