将数组和/或标量的散列转换为标量组合数组的数组

时间:2018-03-25 23:53:07

标签: ruby transformation

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

我不会试图用语言说出我需要的东西,只是给出一些示例输入和它们的预期输出作为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以外的语言解决方案。

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分: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 :(得分:1)

因为我必须让它适用于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