0

I volunteer teaching coding to young girls to code and one of the more advanced ones was trying to use a 2D array for her JavaScript project, but was struggling with the concept of multidimensional arrays. I started putting together a tutorial on arrays and multidimensional arrays to review with her next week, got a little carried away with writing a matrix searching demo, and then realized I don't know a great way of deep copying or creating filled multidimensional arrays that copy the potentially variable-length dimensions of another array (e.g., for storing visited cell data when searching) in JavaScript, which I've only really learned/used within the last year-ish. This is what I came up with:

/**
*    @param mdArray A multidimensional array that may contain variable length arrays
*    @param fillValue The value to fill the cells with
*
*    @return A multidimensional array with the same dimensions as mdArray where
*            each cell is filled with fillValue
*/
function createFilledMultidimensionalArray(mdArray, fillValue) {
    // Create a new array with mdArray.length rows
    return new Array(mdArray.length).fill().map( function (elt, row) {
            // Populate each row with a new filled array containing fillValue
            return new Array(mdArray[row].length).fill(fillValue);
        }
    );
}

/**
*    @param mdArray A multidimensional array that may contain variable length arrays 
*
*    @return A deep copy of mdArray
*/
function multidimensionalArrayCopy(mdArray) {
    return JSON.parse(JSON.stringify(mdArray));
    // note: I'm aware this isn't a universally perfect deep copy *shrug*
}
/* Testing */
// Create a ridiculous array containing variable-length arrays
var multidimensionalArray = [[6, { a: '1', b: 2 }], [1, 2], [3, 4, 5], ['seven']];

// Copy and print the array
var copied = multidimensionalArrayCopy(multidimensionalArray);
console.log(multidimensionalArray);
// Prints: [ [ 6, { a: '1', b: 2 } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]

// Modify a value
multidimensionalArray[0][1].b = 'hi';

// Print both arrays, observe copy is deep
console.log(multidimensionalArray);
console.log(copied);
/* Prints:
[ [ 6, { a: '1', b: 'hi' } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]
[ [ 6, { a: '1', b: 2 } ], [ 1, 2 ], [ 3, 4, 5 ], [ 'seven' ] ]
*/

// Create a new array with same dimensions as 'copied' where each cell is filled with 'false'
console.log(createFilledMultidimensionalArray(copied, false));
/* Prints:
[ [ false, false ],
    [ false, false ],
    [ false, false, false ],
    [ false ] ]
*/

Does anyone else out there with more JS experience have any other ideas? (Please don't suggest slice, which shallow copies.)

Allison
  • 1,925
  • 1
  • 18
  • 25
  • Now I am confused too. Is `new Array(mdArray.length).fill().map..` necessary? If somebody should understand your lesson, the code should be clear and readable. For example I think that `multidimensionalArrayCopy` could simply `return JSON.parse(JSON.stringify(mdArray))` – bigless Mar 17 '18 at 00:59
  • @bigless You are correct... I wasn't thinking when I wrote that. Code is updated. Still wondering if there can be further improvement! – Allison Mar 17 '18 at 01:05
  • And I think that `createFilledMultidimensionalArray` could be written as `return mdArray.map(val => fillValue)` – bigless Mar 17 '18 at 01:05
  • @bigless Oh! I didn't realize you could use stringily on the entire thing. I hadn't seen it suggested in any of the matrix copying questions I'd looked up and didn't think to try it. Thanks! – Allison Mar 17 '18 at 01:07
  • If you are interested in md arrays, you should look up for recursive functions because you probably end up there.. – bigless Mar 17 '18 at 01:10
  • @bigless The second suggestion to return `mdArray.map(val => fillValue)` would just create a single array of mdArray.length fillValues (it would only create rows). It would not work for the multidimensional, variable-length portion – Allison Mar 17 '18 at 01:13
  • You are right. Sorry. It should be `return mdArray.map(arr => arr.map(() => fillValue))` – bigless Mar 17 '18 at 01:17

1 Answers1

0

You could use a recursive clone function instead of stringifying your data. Then for filling multi-dimension arrays, you could use a recursive deepMap function that calls itself on nested arrays:

function clone(value) {
  if(Array.isArray(value)) return value.map(clone);
  if(typeof value === 'object') {
    return Object.entries(value).reduce((cloned, [key, value]) => {
      cloned[key] = clone(value);
      return cloned;
    }, {});
  }
  return value;
}

function deepMap(array, fn) {
  return array.map(value =>
    Array.isArray(value)
      ? deepMap(value, fn)
      : fn(value)
  );
}

function deepFill(array, fillValue) {
  return deepMap(array, () => fillValue);
}

const original = [
  [1, 2, 3],
  [{value: 'unchanged'}, 5, 6],
  [false, true],
  [7, 8, [9, 10], 11, 12],
];

const cloned = clone(original);
const filled = deepFill(original, 'fill');
cloned[1][0].value = 'changed';


console.log(
  original[1][0].value,
  cloned[1][0].value,
);
console.log(filled);
SimpleJ
  • 13,812
  • 13
  • 53
  • 93