Billybobbonnet
Billybobbonnet

Reputation: 3226

How can I create a shallow copy of an array with a map like function?

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

Upvotes: 0

Views: 1089

Answers (3)

VLAZ
VLAZ

Reputation: 29090

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");
}

Upvotes: 2

wfreude
wfreude

Reputation: 508

// 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));

Upvotes: 0

Nurbol Alpysbayev
Nurbol Alpysbayev

Reputation: 21931

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).

Upvotes: 1

Related Questions