【问题标题】:Rotate the contents of the JSON as table将 JSON 的内容旋转为表格
【发布时间】:2021-10-18 17:52:22
【问题描述】:

如果 JSON 为 [1,2,3,4],则表格顺时针旋转一次。在第二个例子中,5 是固定的,所有其他数字都顺时针旋转了一次。系统应提供以下输出并以 JSON 格式打印。

input            output

1  2              3  1
           ->
3  4              4  2


input              output

1  2  3            4  1  2
           ->
4  5  6            7  5  3

7  8  9            8  9  6

如何在 javascript/typescript 中实现这一点?

如果需要任何澄清或您觉得缺少什么,请告诉我。

谢谢各位。

【问题讨论】:

  • 数组是否总能保证有完美的元素个数?当您“旋转”一个长度为四或更多的正方形时,您如何处理外“环”比内环需要更长的时间才能旋转回原位的事实?喜欢,看看this。这真的是你想要的吗?
  • @jcalz,非常感谢。我正在寻找这个解决方案。

标签: javascript json typescript


【解决方案1】:

首先,您需要一个二维矩形数组,而您的输入似乎是一个一维数组。将一维数组“分块”成二维数组很容易:

const chunk = <T,>(arr: T[], size: number): T[][] =>
  Array.from({ length: Math.ceil(arr.length / size) },
    (_, i) => arr.slice(i * size, (i + 1) * size));

让我们看看实际情况;从包含 25 个元素的数组开始:

const width = 5;
const origArray = Array.from({ length: width * width }, (_, i) => (i + 1));
console.log(origArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 
// 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] 

chunked 版本有 5 个元素,每个元素有 5 个元素:

const chunkedArr = chunk(origArray, width);
console.log(chunkedArr); // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], 
// [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

虽然这不是可视化“旋转”的好方法,所以让我们以 2D 形式显示它:

displayArr(chunkedArr); /* "
   1,   2,   3,   4,   5
   6,   7,   8,   9,  10
  11,  12,  13,  14,  15
  16,  17,  18,  19,  20
  21,  22,  23,  24,  25"  */

displayArr() 定义为

const displayArr = <T,>(arr: T[][]) =>
  console.log("\n" + arr.map(c => c.map(v => String(v).padStart(4))).join("\n"))

好的,一旦我们有了一个二维数组,我们如何将它“旋转”一个元素?大概您将每个元素移动一个位置(向上、向右、向下或向左),具体取决于它在数组中的位置。这不是我所知道的真正的旋转,或者至少不是刚性旋转。

相反,您正在做的是将数组划分为“环”,并在其自己的环中顺时针移动每个元素。在上面的 25 元素数组中,有 3 个环。最外面的环(我称之为“环 0”)由十六个元素(1,2,3,4,5,10,15,20,25,24,23,22,21,16,11,6)组成,由八个元素(7、8、9、14、19、18、17、12)组成的中间环(我称之为“环 1”)和最内环(我称之为“环 2”)由一个元素 (13) 组成。

其环顶行中的任何元素,但不在其环的最右列中,应向右移动。然后,其环左列中的任何元素都应向上移动。然后,其环底行中的任何元素都应向左移动。剩下的任何东西都应该向下移动。如果我们可以通过编程方式确定每个元素的“环号”,以及该元素是在其环的顶部、右侧、底部还是左侧,我们就可以相应地调整元素索引。

一个问题是当数组有奇数行和奇数列时,最里面的环不能真正移动。例如,如果您有一个 5×7 数组,则中间会有一个 1×3 环。像“A、B、C”这样的 1×3 环实际上不能去任何地方。 (我的意思是,你可以想出一些其他的方式来改变它,比如“C、A、B”之类的,但这与其他环完全不同。)所以我们需要检测数组是否有这样的一个“固定环”,并且不移动该环中的任何元素。

这是一种可能的实现方式:

function rotateClockwise<T>(arr: T[][]) {
  const ret: any[][] = arr.map(c => c.map(v => "")); // blank copy 
  const rows = arr.length;
  if (rows === 0) return ret;
  const cols = arr[0].length;
  if (!arr.every(l => l.length === cols)) throw new Error("Not rectangular");
  const stationaryRing = (rows % 2 !== 0) && (cols % 2 !== 0) ? 
    (Math.min(rows, cols) - 1) / 2 : -1;
  for (let r = 0; r < rows; r++) {
    let nr = rows - 1 - r;
    for (let c = 0; c < cols; c++) {
      let nc = cols - 1 - c;
      const ring = Math.min(r, nr, c, nc);
      let [rNew, cNew] = [r, c];
      if (ring !== stationaryRing) {
        if (r === ring && nc !== ring) cNew++; // top row moves right (except for rightmost)
        else if (c === ring) rNew--; // left column moves up
        else if (nr === ring) cNew--; // bottom row moves left
        else rNew++; // right column moves down
      }
      ret[rNew][cNew] = arr[r][c];
    }
  }
  return ret;
}

那里的逻辑反映了上述讨论。对于从数组左上角测量的r 行和c 列的每个元素,您还可以计算nrnc,即从数组右下角测量的元素的索引。 ring 数字是其中的最小值。如果ring 不是固定环,那么您可以检查四个运动案例中的每一个。如果r === ring 那么你就在上面。如果c === ring 那么你在左边。如果nr === ring 那么你就在底部。如果(nc === ring) 那么你就在右边。我想我不会再强调这一点了。


让我们看看上面的 25 元素数组是如何工作的:

displayArr(rotateClockwise(chunkedArr)); /* 
   6,   1,   2,   3,   4
  11,  12,   7,   8,   5
  16,  17,  13,   9,  10
  21,  18,  19,  14,  15
  22,  23,  24,  25,  20" */

看起来不错。最外圈旋转一圈,中间圈也是如此,而最内圈没有移动。

为了确保这适用于不同的情况,让我们尝试一个 5×7 数组:

const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abc"+
  "defghijklmnopqrstuvwxyz!@#$%^&*()_-+=[]{}\\|;:'\",<.>/?`~";

const makeArr = (rows: number, cols: number) =>
  Array.from({ length: rows }, (_, r) => 
    Array.from({ length: cols }, (_, c) => chars.charAt(r * cols + c)));

const arr = makeArr(5, 7);
displayArr(arr); /* "
   A,   B,   C,   D,   E,   F,   G
   H,   I,   J,   K,   L,   M,   N
   O,   P,   Q,   R,   S,   T,   U
   V,   W,   X,   Y,   Z,   0,   1
   2,   3,   4,   5,   6,   7,   8" */

displayArr(rotateClockwise(arr)); /* "
   H,   A,   B,   C,   D,   E,   F
   O,   P,   I,   J,   K,   L,   G
   V,   W,   Q,   R,   S,   M,   N
   2,   X,   Y,   Z,   0,   T,   U
   3,   4,   5,   6,   7,   8,   1" */

看起来也不错。您可以看到最里面的Q, R, S 环没有移动,而其他环移动了一个元素。您应该可以随意使用不同的数组,看看它是如何工作的。

Playground link to code

【讨论】:

    【解决方案2】:

    我不确定为什么您的第二个示例看起来是这样,但您似乎想将一维数组重塑为二维矩阵,移动一行,然后转置。

    您能否澄清一下“向右移动一次”的真正含义?

    const reshape = (data, size) =>
      new Array(size[0]).fill(0).map((y, row) =>
        new Array(size[1]).fill(0).map((x, col) =>
          data[row * size[1] + col]));
    
    const shift = (matrix, oldIndex,  newIndex) => {
      const [extracted] = matrix.splice(oldIndex, 1);
      matrix.splice(newIndex, 0, extracted);
      return matrix;
    }
    
    const transpose = (matrix) => matrix[0].map((_, col) =>
      matrix.map(row => row[col]));
    
    const print = (matrix) =>
      console.log(matrix.map(col => col.join(' ')).join('\n'));
    
    print(transpose(shift(reshape([1, 2, 3, 4], [2, 2]), 0, 1)));
    print(transpose(shift(reshape([1, 2, 3, 4,  5, 6, 7, 8, 9], [3, 3]), 0, 1)));

    编辑

    我包含了上面链接的非 TypeScript 版本的 jcalz 代码。

    // Needs to be a perfect square
    const arr = [
       1,  2,  3,  4,  5,
       6,  7,  8,  9, 10,
      11, 12, 13, 14, 15,
      16, 17, 18, 19, 20,
      21, 22, 23, 24, 25
    ];
    
    const format = (matrix) =>
      matrix.map(row => row.map(col =>
        String(col).padStart(4))).join('\n');
    
    const chunk = (arr, size) =>
      Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
        arr.slice(i * size, (i + 1) * size));
    
    let chunked = chunk(arr, Math.round(Math.sqrt(arr.length)));
    
    console.log(`Original:\n${format(chunked)}`)
    
    for (let i = 1; i <= 8; i++) {
      const rotated = Array.from({ length: chunked.length }, (_, r) =>
        Array.from({ length: chunked[r].length }));
    
      const rCenter = (chunked.length - 1) / 2;
      for (let r = 0; r < chunked.length; r++) {
        const dr = r - rCenter;
        const cCenter = (chunked[r].length - 1) / 2;
        for (let c = 0; c < chunked[r].length; c++) {
          const val = chunked[r][c];
          const dc = c - cCenter;
          const ring = Math.max(Math.abs(dr), Math.abs(dc));
          let [rNew, cNew] = [r, c];
          if (ring !== 0) {
            if (dr === -ring && dc !== ring) cNew++; // ➡
            else if (dc === -ring) rNew--;           // ⬆
            else if (dr === ring) cNew--;            // ⬅
            else rNew++;                             // ⬇
          }
          rotated[rNew][cNew] = val;
        }
      }
    
      console.log(`After ${i} rotations:\n${format(rotated)}`);
    
      chunked = rotated; // Update
    }
    
    console.log('Note that the inner ring is back to start while the outer ring is rotated 180 degrees');
    .as-console-wrapper { top: 0; max-height: 100% !important; }

    【讨论】:

    • 感谢您的回答。可能是右移有点混乱,我会稍微改变一下问题。如果您看到输出表,它已顺时针旋转,并且 5 保持在同一位置。 rest all 顺时针旋转了一圈。 @先生。多旋风
    猜你喜欢
    • 2022-01-18
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多