【问题标题】:How to transpose an m*n matrix using recursion?如何使用递归转置 m*n 矩阵?
【发布时间】:2019-12-20 03:22:07
【问题描述】:

我正在尝试使用递归转置矩阵。现在,我知道在正常情况下这不是一个好主意,嵌套循环/嵌套映射或类似方法更好,但出于教育目的,我需要学习这一点。

为了表明我做了功课,这里是嵌套循环方法:

const arrMatrix = [
  [3, 6, 7, 34],
  [6, 3, 5, 2],
  [2, 6, 8, 3]
];

const transposedMatrix = []

for (let i = 0; i < arrMatrix[0].length; i++) {
  const tempCol = [];
  for (let j = 0; j < arrMatrix.length; j++) {
    tempCol.push(arrMatrix[j][i]);
  }
  transposedMatrix.push(tempCol);
}

console.log(transposedMatrix);

这是使用嵌套maps 的另一种方法:

    const arrMatrix = [
      [3, 6, 7, 34],
      [6, 3, 5, 2],
      [2, 6, 8, 3]
    ];

    const transposedMatrix = arrMatrix[0].map((_, i) =>
      arrMatrix.map((_, j) => arrMatrix[j][i])
    );

    console.log(transposedMatrix);

这里有 some resources 我经历过但没能想出使用它们的解决方案。

如果可能的话,除了算法/代码,请给我一些解释和资源,让我了解更多。

【问题讨论】:

    标签: javascript algorithm recursion functional-programming


    【解决方案1】:
      const map = ([head, ...tail], mapper) => tail.length ? [mapper(head), ...map(tail, mapper)] : [mapper(head)];
    
      const transpose = matrix =>
        matrix[0].length 
          ? [map(matrix, row => row.shift()), ...transpose(matrix)]
          :  [];
    

    工作原理:

    从给定的矩阵中,我们总是取出第一列(matrix.map(row =&gt; row.shift()),然后我们继续递归:

     [[1, 1, 1],    ->   [[1, 1],    ->  [[1],   ->   [[],
      [2, 2, 2],          [2, 2],         [2],         [],
      [3, 3, 3]]          [3, 3]]         [3]]         []]
    

    然后达到基本情况,矩阵为空(matrix[0].length 为 0 = falsy)并返回一个空数组。现在在每一步中,取出的列都会添加到该数组中,因此它现在是一行:

       [[1, 2, 3],    <-    [[1, 2, 3],    <-  [[1, 2, 3]]   <-  []
        [1, 2, 3],            [1, 2, 3]]    
        [1, 2, 3]]
    

    注意:这会破坏原始数组

    【讨论】:

    • 这看起来很不错,但是你能给我一些背景信息吗?解释?链接可能吗?
    • 还有一个map - 你在作弊:D
    • @JonasWilms 非常感谢。已被接受为正确答案。我想知道您是否还可以添加一个变体,其中不使用 map 以确保完整性。
    • @alireza 当然。我还会添加一个非变异版本
    【解决方案2】:
    const transpose = matrix => {
      const row = (x) => x >= matrix[0].length ? [] : [col(x, 0), ...row(x + 1)];
      const col = (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(x, y + 1)];
      return row(0);
    };
    

    该版本不会改变原始数组。您可以更进一步,而不是 纯粹的功能性,但这有点矫枉过正:

     const transpose = matrix => (
       (row, col) => row(row)(col)(0)
     )(
        row => col => (x) => x >= matrix[0].length ? [] : [col(col)(x, 0), ...row(row)(col)(x + 1)],
        col => (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(col)(x, y + 1)]
     );
    

    【讨论】:

    • 我看到了那个 U 组合器 :)
    • @user633183 老实说,我没有用真正的 U 组合函数来做到这一点......
    • 所有x(x)... 都可以替换为U(x),其中U = f =&gt; f (f) - 附带说明,此transpose 删除了最后一列。即在OP中使用arrMatrix,输出为[[3,6,2],[6,3,6],[7,5,8]];缺少...[34,2,3]]。这是因为matrix.length被用作常量,所以3被用于行和列中的退出条件。
    • 啊对,应该是固定的...是的,但是 U 引入了另一个变量。
    【解决方案3】:

    这与 hitmands 的解决方案非常相似,并且可能性能较差,但我认为避免使用列索引会稍微干净一些:

    const head = xs => xs[0]
    const tail = xs => xs.slice(1)
    
    const transpose = (m) => head(m).length
      ? [m.map(head), ...transpose(m.map(tail))]
      : []
    
    const matrix = [
      [3, 6, 7, 34],
      [6, 3, 5, 2],
      [2, 6, 8, 3]
    ]
    
    console .log (
      transpose (matrix)
    )

    此版本将第一列转置为一行(通过.map(head)),然后在剩余的矩阵上重复(通过.map(tail)),当第一行为空时触底。 如果您愿意,您可以内联这些辅助函数,使其看起来像这样:

    const transpose = (m) => m[0].length
      ? [m.map(xs => xs[0]), ...transpose(m.map(xs => xs.slice(1)))]
      : []
    

    ..但我不会推荐它。第一个版本似乎更具可读性,headtail 很容易重复使用。

    更新

    user633183 建议另一种转义条件。这是一个很好的问题,对于格式错误的数据,它是否是一个更好的结果,但它肯定是一个有用的可能变体:

    const head = xs => xs[0]
    const tail = xs => xs.slice(1)
    const empty = xs => xs.length == 0
    
    const transpose = (m) => m.some(empty)
      ? []
      : [m.map(head), ...transpose(m.map(tail))]
    

    (这也可以用m.every(nonempty)通过反转条件表达式中的结果和替代来完成,但我认为它会稍微难以阅读。)

    【讨论】:

    • 比我写的two years agotranspose 干净多了。我现在要改变的一件事是退出条件head(m).lengthm.every(x =&gt; x.length) -- 当我们想象像[[1,2,3], [4], [5, 6, 7]] 这样的输入时,head(m).length 很容易受到攻击。第一个递归调用将是[[2,3], [], [6,7]],其中head([])undefined——m.every(...) 避免了这个陷阱。
    • @user633183:是的,这些年来我已经编写了几个版本的transpose,但这是我第一次尝试通过递归明确地做到这一点。不出所料,这会导致代码更具可读性。我已更新以包含您的建议作为替代方案,但这是一个有趣的问题,结果应该是您建议的 [[1, 4, 5]] 还是当前的 [[1,4, 5], [2, null, 6], [3, null, 7]] 以及这个格式不正确的 (?) 数据。
    • 这是一个很好的答案,斯科特。我应该先这么说。递归是一种毒药^_^ 我认为nulls 还可以;我们必须注意undefined 的稀疏数组,即[ 1, 2, , 4 ]。我有一个想法为此使用Maybe,并将我的答案添加到这个线程。今天晚些时候我会看看我是否可以调整它以更好地处理稀疏数组。
    • 是的,递归是地狱般的(地狱般的(地狱般的(地狱般的......药物))......)!
    • 除非我试图确保一个完整的功能(正如你的优秀答案所做的那样),我试图假装稀疏数组不存在,并认为那些提供一个的人应该得到他们得到的! :-)
    【解决方案4】:

    我会这样写, 假设矩阵内的所有行都具有相同的长度:

    1. 检查是否还有要处理的行
    2. 从给定索引处的每个列创建一行
    3. 将列索引增加 1
    4. 使用新索引调用转置

    const transpose = (m, ci = 0) => ci >= m[0].length 
      ? [] 
      : [m.map(r => r[ci]), ...transpose(m, ci + 1)]
    ;
    
    
    const matrix = [
      [3, 6, 7, 34],
      [6, 3, 5, 2],
      [2, 6, 8, 3]
    ];
    
    console.log(
      transpose(matrix),
    );

    【讨论】:

    • 简洁明了的解释,兄弟 :)
    【解决方案5】:

    我有一个想法,使用 Maybe monad 编写 transpose。我将开始使用函数式操作,然后重构以清理代码 -

    依赖关系 -

    const { Just, Nothing } =
      require("data.maybe")
    
    const safeHead = (a = []) =>
      a.length
        ? Just(a[0])
        : Nothing()
    
    const tail = (a = []) =>
      a.slice(1)
    

    没有重构 -

    const column = (matrix = []) =>
      matrix.reduce
        ( (r, x) =>
            r.chain(a => safeHead(x).map(x => [ ...a, x ]))
        , Just([]) 
        )
    
    const transpose = (matrix = []) =>
      column(matrix)
        .map(col =>
          [ col, ...transpose(matrix.map(tail)) ]
        )
        .getOrElse([])
    

    使用通用 appendlift2 重构 column -

    const append = (a = [], x) =>
      [ ...a, x ]
    
    const lift2 = f =>
      (mx, my) =>
        mx.chain(x => my.map(y => f(x, y)))
    
    const column = (matrix = []) =>
      matrix.reduce
        ( (r, x) =>
            lift2(append)(r, safeHead(x))
        , Just([])
        )
    
    const transpose = (matrix = []) =>
      column(matrix)
        .map(col =>
          [ col, ...transpose(matrix.map(tail)) ]
        )
        .getOrElse([])
    

    使用通用转换器 mapReduce 再次重构 column -

    const mapReduce = (map, reduce) =>
      (r, x) => reduce(r, map(x))
    
    const column = (matrix = []) =>
      matrix.reduce
        ( mapReduce(safeHead, lift2(append))
        , Just([]) 
        )
    
    const transpose = (matrix = []) =>
      column(matrix)
        .map(col =>
          [ col, ...transpose(matrix.map(tail)) ]
        )
        .getOrElse([])
    

    transpose 在每个重构步骤中保持不变。它产生以下输出 -

    transpose
      ( [ [ 1, 2, 3, 4 ]
        , [ 5, 6, 7, 8 ]
        , [ 9, 10, 11, 12 ]
        ]
      )
      // [ [ 1, 5, 9 ]
      // , [ 2, 6, 10 ]
      // , [ 3, 7, 11 ]
      // , [ 4, 8, 12 ]
      // ]
    
    transpose
      ( [ [ 1, 2, 3, 4 ]
        , [ 5 ]
        , [ 9, 10, 11, 12 ]
        ]
      )
      // [ [ 1, 5, 9 ] ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      • 1970-01-01
      • 2021-10-28
      • 2018-09-06
      • 2013-08-04
      相关资源
      最近更新 更多