Búsqueda de sitios web

javascript - ¿Por qué los valores no se asignan correctamente a mi arreglo?


Estoy intentando resolver este problema de leetcode (juego de la vida): https://leetcode.com/problems/game-of-life

Este es mi código:

function isLive(board: number[][], row: number, column: number) {
  let liveNeighbours = 0;
  [row - 1, row, row + 1].forEach(r => {
    if (r < 0 || r >= board.length) return;
    [column - 1, column, column + 1].forEach(c => {
      if (c === column && r === row) return;
      if (c < 0 || c >= board[0].length) return;
      if (board[r][c] === 1) liveNeighbours++
    })
  })

  if (board[row][column] === 1 && (liveNeighbours === 3 || liveNeighbours === 2)) return true;
  if (board[row][column] === 0 && liveNeighbours === 3) return true;

  return false;
}


/**
Do not return anything, modify board in-place instead.
*/
function gameOfLife(board: number[][]): void {

  let nextGen = Array(board.length).fill(Array(board[0].length).fill(0));

  for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
      console.log("isLive row ", i, " col ", j, isLive(board, i, j))
      if (isLive(board, i, j)) nextGen[i][j] = 1
    }
  }

  console.log("next gen", nextGen);

  board = nextGen;
};

Ejecuté el código con esta entrada:

[
 [0,1,0],
 [0,0,1],
 [1,1,1],
 [0,0,0]
]

Y esto lo produce (básicamente lo mismo):

[
 [0,1,0],
 [0,0,1],
 [1,1,1],
 [0,0,0]
]

Mientras espera esto:

[
 [0,0,0],
 [1,0,1],
 [0,1,1],
 [0,1,0]
]

Ahora, lo que realmente me vuelve loco, son los registros de impresión. Cuando imprimo para cada celda si debería estar activa o no, vuelve perfectamente como debería ser. Este es el registro:

isLive row  0  col  0 false
isLive row  0  col  1 false
isLive row  0  col  2 false
isLive row  1  col  0 true
isLive row  1  col  1 false
isLive row  1  col  2 true
isLive row  2  col  0 false
isLive row  2  col  1 true
isLive row  2  col  2 true
isLive row  3  col  0 false
isLive row  3  col  1 true
isLive row  3  col  2 false

Además, en el último registro para imprimir nextGen, imprime esto: next gen [ [ 1, 1, 1 ], [ 1, 1, 1 ], [ 1, 1, 1 ], [ 1, 1, 1 ] ] Básicamente reasigna todas las celdas a 1, aunque puedo ver que no todas las afirmaciones son verdaderas.

Lo cual no se parece a nada. No encaja con la devolución individual que obtengo para cada celda, e incluso no coincide con lo que afirma que estoy devolviendo a pesar de que básicamente está al final. ¿Me estoy perdiendo algo sobre cómo asignar valores a los arrays?

EDIT: En lugar de simplemente asignar board=nextGen, también he intentado lo siguiente:

for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
        board[i][j] = nextGen[i][j];
    }
}

Ahora obtengo esto como salida [[1,1,1],[1,1,1],[1,1,1],[1,1,1]] (la impresión de nextGen).

EDICIÓN 2: He intentado cambiar la función para simplemente devolver el conteo de vecinos y hacer la condicional después. Aunque aún no he llegado a mover la condicional, ya puedo ver que todavía existe un problema al asignar el elemento al nuevo arreglo.

Este es mi código modificado (de nuevo, no está completo, pero el resto no es relevante para el problema en este momento):

function countNeighbors(board: number[][], row:number, column:number){
    let liveNeighbors = 0;
    [row-1, row, row+1].forEach(r=>{
        if(r<0 || r>= board.length) return;
        [column-1, column, column+1].forEach(c=>{
            if(c === column && r=== row) return;
            if(c<0 || c>= board[0].length) return;
            if(board[r][c]===1) liveNeighbors++
        })
    })

    return liveNeighbors;
}


/**
 Do not return anything, modify board in-place instead.
 */
function gameOfLife(board: number[][]): void {

let neighbors = Array(board.length).fill(Array(board[0].length).fill(0));

    for (let i = 0; i<board.length; i++){
        for (let j = 0; j<board[0].length; j++){
            let numberOfNeighbors = countNeighbors(board,i,j);
            console.log(i, j, numberOfNeighbors)
            neighbors[i][j] = numberOfNeighbors
        }
        console.log("neighbors", neighbors)
    }
};

He añadido un registro en la consola después de cada iteración de i, y esto es lo que imprime:

0 0 1
0 1 1
0 2 2
neighbors [ [ 1, 1, 2 ], [ 1, 1, 2 ], [ 1, 1, 2 ], [ 1, 1, 2 ] ]
1 0 3
1 1 5
1 2 3
neighbors [ [ 3, 5, 3 ], [ 3, 5, 3 ], [ 3, 5, 3 ], [ 3, 5, 3 ] ]
2 0 1
2 1 3
2 2 2
neighbors [ [ 1, 3, 2 ], [ 1, 3, 2 ], [ 1, 3, 2 ], [ 1, 3, 2 ] ]
3 0 2
3 1 3
3 2 2
neighbors [ [ 2, 3, 2 ], [ 2, 3, 2 ], [ 2, 3, 2 ], [ 2, 3, 2 ] ]

Básicamente, cada fila se duplica en todas las filas. Eso parece ser lo que está afectando mi función de cualquier manera que lo haya hecho, pero ¿por qué está pasando eso?

¿Tiene algo que ver con el comportamiento de fill?

Edición 3: Ok, entonces se trata de cómo se crea el nuevo arreglo. Si lo creo así para mi caso de prueba: let neighbors=[[],[],[],[]]; Entonces todo funciona perfectamente.

Me encantaría saber, sin embargo, ¿cuál es la causa de este comportamiento? ¿Hay una manera ordenada de crear este arreglo que no cause esto? Puedo simplemente agregar nuevos arreglos vacíos en cada iteración de i, lo cual funcionaría y no es muy demandante, pero pensé que la forma en que lo hice primero era más elegante.

Respuesta #1

Básicamente, lo que está ocurriendo aquí es que en function gameOfLife(), cuando asignas board=nextGen;, en realidad solo estás asignando la variable board (el nombre) dentro de la función para que se refiera al objeto nextGen, pero no estás cambiando los datos en board. Básicamente, estás haciendo que el nombre board se refiera a nextGen mientras dejas los datos que antes eran referidos por board iguales.

Lo que necesitas hacer es, en lugar de decir board =, usar métodos para copiar los datos contenidos de nextGen a board. Así que una manera simple de hacer eso es esta:

for (let i = 0; i < board.length; i++) {
    board[i] = nextGen[i];
}

Editar :

Dado que originalmente, nextGen parecía estar correcto al imprimir para cada celda, eso es muy extraño. Podría intentar asegurarme de que todos los corchetes y puntos y comas estén incluidos solo para asegurar que el flujo de control de todas las declaraciones if funcione como se espera, ya que solo hay un lugar donde nextGen debería poder establecerse en 1 y la declaración if vecina a veces no debería incluirla.

De lo contrario, ¿qué tal si intentas una estrategia completamente diferente que no utilice un objeto nextGen en absoluto? Lo que podrías intentar hacer es, en lugar de crear un nuevo tablero para la siguiente generación, simplemente obtener el número de vecinos que tiene cada casilla. Luego, puedes modificar directamente el tablero según las reglas para el número de vecinos y el valor de vida actual sin necesidad de preocuparte por copiar datos. Así que:

let neighbors = Array(board.length).fill(Array(board[0].length).fill(0));

// ============================================================================================================
// Put the code for finding the number of neighbors here, inserting it into the corresponding spot in neighbors
// ============================================================================================================

for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
        if (board[i][j] === 1) { // Only change things if conditions are met for death
            if (neighbors[i][j] < 2 || 3 < neighbors [i][j]) {
                board[i][j]--;
            }
        } else { // === 0; only change things if conditions are met for new life
            if (neighbors[i][j] === 3 {
                board[i][j]++;
            }
        }
    }
}