0

If I have a 4x4 2d Array and want to work on a specific column, I can either get it using a map and set it back or I can get a shallow copy of it and work directly on the original array values.

The get/set way

// create the original array
let arr = [...Array(4)].map(e => Array(4).fill(0))
console.log(arr)

// get a specific column, e.g. the 3d
let myColumn = arr.map(tile => tile[3])

// change it
myColumn[2] = 1

// set it back
arr.map((line, i) => line[3] = myColumn[i])

console.log("modified array", arr)
Now, how can I achieve the same thing with a shallow copy, i.e. without having to set the value back?

UPDATE

Here are my (ugly) getter/setter functions, probably very perfectible. It does not even properly deep copy in each case (e.g. the get with index > 11) but it still does the job.

  const getLine = (index) => {
    if (index < 4) return field.map(fieldLine => fieldLine[index])
    if (index > 3 && index < 8) return field[index - 4].slice().reverse()
    if (index > 7 && index < 12) return field.map(fieldLine => fieldLine[Math.abs(index - 11)]).slice().reverse()
    if (index > 11) return field.slice()[Math.abs(index - 15)]
  }
  const setLine = (index, line) => {
    if (index < 4) field.map((fieldLine, i) => fieldLine[index] = line[i])
    if (index > 3 && index < 8) field[index - 4] = line.reverse()
    if (index > 7 && index < 12) field.slice().reverse().map((fieldLine, i) => fieldLine[Math.abs(index - 11)] = line[i])
    if (index > 11) field[Math.abs(index - 15)] = line
  }

FYI, the original problem is here: https://www.codewars.com/kata/4-by-4-skyscrapers

Billybobbonnet
  • 3,156
  • 4
  • 23
  • 49
  • 4
    I don't get what the actual goal is here. You want to set one value on the grid? And somehow use a shallow copy for that? It seems a bit like [an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) - can you describe what the underlying problem is? – VLAZ Jun 19 '19 at 11:26
  • I am trying to improve the performance of the skyscraper 4x4 kata from codeWars https://www.codewars.com/kata/4-by-4-skyscrapers with hope to tackle the 6x6 version. It requires (the way I do it) to get a line or column from every perspective, and update the possible values of each cell. I assume it would be a better practice to work directly on the array, and it is easier to do it from the line I am iterating on – Billybobbonnet Jun 19 '19 at 11:29
  • why not use a setter function and switch the indices? any other solutions, like `Proxy` are slower than a simple `for` loop, even the array methods are slower. – Nina Scholz Jun 19 '19 at 11:34
  • 2
    You cannot make a shallow copy of primitive values. If you need to work with columns as if they are rows (single array) and you don't want to do a [matrix transposition](https://stackoverflow.com/questions/17428587/transposing-a-2d-array-in-javascript) all the time, then perhaps you can create *two* 4x4 grids - one contains the rows as each member, the other is *effectively* the transposed first one. You can add the same objects to both, so `rows[0][2]` will correspond to `columns[2][0]` and thus you can grab a column or a row as needed then update the object and both will change. – VLAZ Jun 19 '19 at 11:36
  • If you consider it an XY problem, I can delete the question. However, if the question still makes sense, since @VLAZ suggestion could be an answer, you are welcome to write it as such :) – Billybobbonnet Jun 19 '19 at 11:40
  • @Billybobbonnet I can give you *some* sort of solution. Not sure if that will work exactly but it might at least give you some ideas. Give me a moment to write up an answer. – VLAZ Jun 19 '19 at 11:45
  • @NinaScholz I do use a setter function (see updated question), but I was looking for another/more optimized approach. It looks like there is no simple answer on that (besides "you cannot make a shallow copy of primitive values"). Thank you for your input – Billybobbonnet Jun 19 '19 at 11:48

3 Answers3

2

You cannot make a "shallow copy" of primitive values - when you make arr.map(tile => tile[3]) you are making a new array with new values, so changing one doesn't change the other.

However, you can make an array of objects, as the values of objects are their references

let grid = [
  [{value: 1}, {value: 2}],
  [{value: 3}, {value: 4}],
];

//take a column
let col2 = grid.map(row => row[1]);

//change one value
col2[1].value = 42;

//the original changed
console.log(grid);

If you need to make frequent changes based on columns, you don't need to do map(row => row[columnNumber]) every time or even matrix transposition to effectively rotate the grid. You can simply make two grids - one representing columns, the other rows. If you start with the "normal" grid first, fill it with objects and then transpose it, then you will have effectively two views over the same data:

let rows = [
  [ {cell: "a1"}, {cell: "a2"}, {cell: "a3"}, {cell: "a4"} ],
  [ {cell: "b1"}, {cell: "b2"}, {cell: "b3"}, {cell: "b4"} ],
  [ {cell: "c1"}, {cell: "c2"}, {cell: "c3"}, {cell: "c4"} ],
  [ {cell: "d1"}, {cell: "d2"}, {cell: "d3"}, {cell: "d4"} ]
];

//transpose
let columns = rows[0].map((col, i) => rows.map(row => row[i]));

//show 
console.log("rows:");
console.log(format(rows));
console.log("----");
console.log("columns:");
console.log(format(columns));
console.log("----");

//take a column
let col2 = columns[1];

//update a value
col2[2].cell = "XX";

//show again
console.log("after the change");
console.log("rows:");
console.log(format(rows));
console.log("----");
console.log("columns:");
console.log(format(columns));
console.log("----");

//helper function to display the grids more compactly
function format(arr) {
   return arr
     .map(arr => arr.map(({cell}) => cell).join())
     .join("\n");
}
VLAZ
  • 26,331
  • 9
  • 49
  • 67
1

If you want to make a shallow copy you have to use an object, and not a primitive.

The code could look like this then:

// filling the array:
/*...*/.fill({value: 0})
// changing the value:
myColumn[2].value = 1

I don't know how resource-sensitive your app, but this way your memory consumption will go up by some degree (depends on the app).

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
0

// create the original array
let arr = [...Array(4)].map(e => Array(4).fill(0))
console.log(arr)

// get copy of arr with changed column
const cloneSquareAndChangeColumn = (square, colIndex, newValue) => {
  return square.map((row, col) => {
    return [...row.slice(0, colIndex), newValue, ...row.slice(colIndex + 1)];
  });
}
// test changing last column
console.log("cloned and changed arr: \n", cloneSquareAndChangeColumn(arr, 3, 1));
wfreude
  • 492
  • 3
  • 10