Convert a hash of arrays and / or scalars to an array of arrays of combinations of scalars

I believe that this should be an easy problem for someone, while I spent a huge amount of time searching for a solution and could not find the one I like.

I will not try to say in words what I need, but simply give some examples of inputs and their expected results as Rspec code:

Method:

def explode(hash)
  ...
end

And specification:

describe '#explode' do
  it do
    expect(explode({:a => 1, :b => 2})).
      to eq [[:a, 1, :b, 2]]
  end

  it do
    expect(explode({:a => 1, :b => [2,3,4]})).
      to eq [
        [:a, 1, :b, 2],
        [:a, 1, :b, 3],
        [:a, 1, :b, 4]
      ]
  end

  it do
    expect(explode({:a => [1,2], :b => [3,4]})).
      to eq [
        [:a, 1, :b, 3],
        [:a, 2, :b, 3],
        [:a, 1, :b, 4],
        [:a, 2, :b, 4]
      ]
  end

  it do
    expect(explode({:a => 1, :b => [2,3], :c => [4,5,6]})).
      to eq [
        [:a, 1, :b, 2, :c, 4],
        [:a, 1, :b, 3, :c, 4],
        [:a, 1, :b, 2, :c, 5],
        [:a, 1, :b, 3, :c, 5],
        [:a, 1, :b, 2, :c, 6],
        [:a, 1, :b, 3, :c, 6]
      ]
  end
end

Solutions in languages ​​other than Ruby are also welcome.

+4
source share
3 answers

Array # product seems appropriate here.

h1 = {:a => 1, :b => 2}
h2 = {:a => 1, :b => [2,3,4]}
h3 = {:a => [1,2], :b => [3,4]}
h4 = {:a => 1, :b => [2,3], :c => [4,5,6]}

def explode hash
  a, *b = hash.transform_values { |v| [*v] }.values.unshift
  a.product(*b).map { |ar| hash.keys.zip(ar).flatten }.sort_by(&:last)
end

p explode h1  
 #[[:a, 1, :b, 2]]
p explode h2
 #[[:a, 1, :b, 2],
 # [:a, 1, :b, 3],
 # [:a, 1, :b, 4]]

p explode h3
 #[[:a, 1, :b, 3],
 # [:a, 2, :b, 3],
 # [:a, 1, :b, 4],
 # [:a, 2, :b, 4]]

p explode h4
 #[[:a, 1, :b, 2, :c, 4],
 # [:a, 1, :b, 3, :c, 4],
 # [:a, 1, :b, 2, :c, 5],
 # [:a, 1, :b, 3, :c, 5],
 # [:a, 1, :b, 2, :c, 6],
 # [:a, 1, :b, 3, :c, 6]]

, , . , .

+2

Array # product .

def explode(hash)
  first, *rest = hash.map { |k,v| [k].product([*v]) }
  first.product(*rest).map(&:flatten)
end

h = { :a =>1, :b =>[2,3], :c =>[4,5,6] }    
explode h
  #=> [[:a, 1, :b, 2, :c, 4], [:a, 1, :b, 2, :c, 5], [:a, 1, :b, 2, :c, 6], 
  #    [:a, 1, :b, 3, :c, 4], [:a, 1, :b, 3, :c, 5], [:a, 1, :b, 3, :c, 6]]

, h ,

first, *rest = h.map { |k,v| [k].product([*v]) }
  #=> [[[:a, 1]], [[:b, 2], [:b, 3]], [[:c, 4], [:c, 5], [:c, 6]]]
first
  #=> [[:a, 1]]
rest
  #=> [[[:b, 2], [:b, 3]], [[:c, 4], [:c, 5], [:c, 6]]]

first.product(*rest)
  #=> [[[:a, 1], [:b, 2], [:c, 4]], [[:a, 1], [:b, 2], [:c, 5]],
  #    [[:a, 1], [:b, 2], [:c, 6]], [[:a, 1], [:b, 3], [:c, 4]],
  #    [[:a, 1], [:b, 3], [:c, 5]], [[:a, 1], [:b, 3], [:c, 6]]]

, [*1] #=> [1], [*:a] #=> [:a] [*[1,2]] #=> [1,2], , [*k] k , , [*k] k, k - .

+2

Because I had to get this to work for Ruby <2.4 (where there are no transform_values) - and also because I don't need sorted arrays, I ended up with:

def explode(hash)
  hash.each do |k,v|
    if not hash[k].is_a?(Array)
      hash[k] = [hash[k]]
    end
  end
  a, *b = hash.values.unshift
  a.product(*b).map do |arr|
    hash.keys.zip(arr).flatten
  end
end
+1
source

Source: https://habr.com/ru/post/1695317/


All Articles