将数组 and/or 标量的散列转换为标量组合数组的数组

Transform hash of arrays and/or scalars into an array of arrays of combinations of scalars

我觉得这对某些人来说一定是个简单的问题,而我花了惊人的时间试图找到一个解决方案,但找不到我喜欢的解决方案。

我不会尝试用语言说出我需要什么,只是给出一些示例输入及其预期输出作为 Rspec 代码:

方法是:

def explode(hash)
  ...
end

规格:

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

也欢迎使用 Ruby 以外语言的解决方案。

Array#product 似乎很适合这里。

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]]

为了使我的方法起作用,我必须重新映射值,以便它们都是数组,这并不理想。但我仍然发布了这个答案,因为它可能会给你或其他人一个起点。

因为我必须让它在 Ruby < 2.4(没有 transform_values)的情况下工作 - 而且,因为我不需要对数组进行排序,所以我最终得到了:

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

您可以使用 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 是一个数组。