Ruby:多维数组转换为一维行

4
在 Ruby 中,如何实现以下操作:
[
  1, 
  ["green", "blue", "black"], 
  [ ["1", "2"], ["3"], ["4", "5"] ]
]

转化为这样?

[
  [1, "green", "1"],
  [1, "green", "2"],
  [1, "blue", "3"],
  [1, "black", "4"],
  [1, "black", "5"],
]

我尝试使用 .zip 但没有成功。非常感谢任何帮助,当然我正在寻找一个高效的解决方案。


1
当您提供示例时,为每个输入对象分配一个变量(例如,arr = [1,["green","blue","black"],[["1","2"],["3"],["4","5"]]])会很有帮助。这样,读者可以在答案和评论中引用变量,而无需定义它们,并且所有读者都将使用相同的变量名称。 - Cary Swoveland
3个回答

3

我对这个模式并不是完全清楚,但是根据你提供的例子,这个代码可以得到期望的输出结果:

data[1].
  zip(data[2]).
  flat_map { |x, ys| [x].product(ys) }.
  map { |zs| [data[0], *zs] }
#=> [[1, "green", "1"], [1, "green", "2"], [1, "blue", "3"], 
#    [1, "black", "4"], [1, "black", "5"]]

2

我们现在要处理的是

arr = [1, ["green", "blue", "black"], [ ["1", "2"], ["3"], ["4", "5"] ]]

以下是几种实现所需结果的方法。 #1
arr[1].flat_map.with_index { |color,i| [arr[0]].product([color], arr[2][i]) }
  #=> [[1, "green", "1"], [1, "green", "2"], [1, "blue", "3"],
  #    [1, "black", "4"], [1, "black", "5"]]

步骤如下:
enum0 = arr[1].flat_map
  #=> #<Enumerator: ["green", "blue", "black"]:flat_map>
enum1 = enum0.with_index
  #=> #<Enumerator: #<Enumerator: ["green", "blue", "black"]:flat_map>:with_index>

enum1可以被看作是一个复合枚举。我们可以将其转换为数组来查看将会被生成的enum1的值,并传递到代码块中。

enum1.to_a
  #=> [["green", 0], ["blue", 1], ["black", 2]]

第一个值被生成并传递给块,块变量被赋值并执行块计算。

color, i = enum1.next
  #=> ["green", 0]
color
  #=> "green"
i #=> 0
[arr[0]].product([color], arr[2][i])
  #=> [1].product(["green"], )
  #=> [[1, "green", "1"], [1, "green", "2"]]

对于由enum1生成的另外两个元素,计算方式类似。

另一种方法是复制arr[2]并移动副本的元素:

a2 = arr[2].dup
arr[1].flat_map { |color,i| [arr[0]].product([color], a2.shift) }

#2

arr[1].zip(arr[2]).flat_map { |color, a| [arr[0]].product([color], a) }
  #=> [[1, "green", "1"], [1, "green", "2"], [1, "blue", "3"],
  #    [1, "black", "4"], [1, "black", "5"]]

步骤如下:
b = arr[1].zip(arr[2])
  #=> [["green", ["1", "2"]], ["blue", ["3"]], ["black", ["4", "5"]]]

b[0]被传递到flat_map,块变量被赋值并进行块计算。

color, a = b[0]
  #=> ["green", ["1", "2"]]
color
  #=> "green"
a #=> ["1", "2"]
[arr[0]].product([color], a)
  #=> [["1"]].product(["green"], ["1", "2"])
  #=>  [[1, "green", "1"], [1, "green", "2"]]

在将b的剩余元素传递给map之后,通过Enumerable#flat_map返回所需的数组。

1
谢谢,@tokland。作为一个flat_map的信徒,我应该早就看到了。我已经编辑过了。 - Cary Swoveland

1

我需要比提出的方案更加通用和灵活的解决方案(我的错,我应该更清楚地说明要求),因此我想出了以下解决方案:

class Array
  def transpose_rows
    arys = self.select{|el| el.is_a?(Array)}
    if arys.size == 0
      [self]
    else 
      result = []
      (arys.map(&:size).max || 1).times.map{ |i|
        self.map { |r| 
          r.is_a?(Array) ? r[i] : r
        }.transpose_rows.map{|r| result << r}
      }
      result
    end
  end
end

最初的规范是数组中的每个元素都是一个值或另一个不同深度的数组。每个子数组都将深度-1的子数组的值“展开”为任意数量的“子值”。结果应该是一组行,列出从原始数组派生的所有组合。

其他提出的解决方案对我发布的原始数组确实有效,但这个解决方案适用于更复杂的情况,例如以下情况:

[
  2, 
  [3,4,5], 
  6, 
  [7,8,9], 
  [ [11,22], [33], [44,55] ], 
  [0, 1, 2],
  [ [66], [77], [nil,99] ],
  4
].transpose_rows

# => [
#   [2, 3, 6, 7, 11, 0, 66, 4], 
#   [2, 3, 6, 7, 22, 0, nil, 4], 
#   [2, 4, 6, 8, 33, 1, 77, 4], 
#   [2, 5, 6, 9, 44, 2, nil, 4], 
#   [2, 5, 6, 9, 55, 2, 99, 4]
# ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接