JCLaHoot
JCLaHoot

Reputation: 1054

Nested mapping with 2d arrays: I need the index of my outer arrays

I'm making a puzzle game that relies on elements being placed in grids and evaluating the values at certain x,y locations. My data is stored in 2d arrays, so regular maps don't work that well. As a result, I made this helper function to map over every single cell in a 2d array.

// makes transformations on the cell level in a 2d array
export const twoDMap = (twoDimensionalArray, mapFunction) => {
  return twoDimensionalArray.map((row, y) => {
    return row.map(mapFunction);
  })
}

This works fine most of the time, except when I need to use the x,y indexes of a cell in my 2d map. In the test case below, I can't seem to access the y value of the cells. (I got rid of the ES6 syntax so it'll run in the console)

var testArray = [[true, "random-string", 3],
                 ["potato", false, 4],
                 [null, 4, 5]];

function twoDMap(twoDimensionalArray, mapFunction) {
 return twoDimensionalArray.map(function (row, y) {
   return row.map(mapFunction);
 })
}

function logCells(cells)  {
  twoDMap(cells ,function(cell, x) {
    console.log(cell, "location: ", x);
    // console.log(cell, "location: ", x, y); //throws an error
  })
}

logCells(testArray);

I'm pretty sure I need to make some kind of change to twoDMap, and it's probably quite minor, but I've been on this problem for hours and still can't get it to work. When I write it out manually (using regular nested maps instead of twoDMap ), it works fine, but it's overly verbose and I'd like to avoid it considering that it's a common pattern in this project. This is the common suggestion that I keep finding whenever I google this problem, but it's not a viable solution given how much nesting I need to do for this project.

Essentially, I'm looking for a way to modify twoDMap or the callbacks that are sent into it, so that my callbacks can access the x and y indexes of the 2d array sent to twoDMap.

Upvotes: 1

Views: 1473

Answers (2)

Andre M
Andre M

Reputation: 7534

The issue is that the 'y' is out of scope to the current function, so it can't 'see' the value. For this reason you need to work out how to make it available to the second function. The two approaches I see:

  • Make second function's map an inner operation, within twoDimensionalArray.map(function (row, y)
  • Find a way to pass 'y' to the second function

For the first approach:

var testArray = [[true, "random-string", 3],
                 ["potato", false, 4],
                 [null, 4, 5]];

function twoDMap(twoDimensionalArray) {
 return twoDimensionalArray.map(function (row, y) {
   return row.map(function (row, x) {
     console.log(cell, "location: ", x, y);
   }
 })
}    

twoDMap(testArray);

Below takes the second approach, using the bind() function:

var testArray = [[true, "random-string", 3],
                 ["potato", false, 4],
                 [null, 4, 5]];

function twoDMap(twoDimensionalArray, mapFunction) {
 return twoDimensionalArray.map(function (row, y) {
   return row.map(mapFunction.bind(this, y);
 })
}

function logCells(cells)  {
  twoDMap(cells , function(y, cell, x) {    
    console.log(cell, "location: ", x, y);
  })
}

logCells(testArray);

When binding a values to a function always remember the first parameter should correspond to how you wish to define this and then the subsequent parameters are provided as initial parameters to the function you are calling.

I should add that sometimes using the traditional for loops makes for more manageable code, and sometimes benefit from better performance. An alternative, using for loops:

var testArray = [[true, "random-string", 3],
                 ["potato", false, 4],
                 [null, 4, 5]];

function twoDMap(twoDimensionalArray) {
  for (var y=0; y<twoDimensionalArray.length; y++) {
     for (var x=0; x<twoDimensionalArray[y].length; x++) {
        console.log(twoDimensionalArray[y][x], "location: ", x, y);
     }
  }
}

twoDMap(testArray);

Upvotes: 1

JCLaHoot
JCLaHoot

Reputation: 1054

Turns out, re-implementing the inner map as a for loop and then manually passing the coordinates to the transforming callback does the trick. The callback in array.map has to follow a set format for its arguments ( callback(element, index, array)) but by having the callback act alone in a for loop, that restriction is lifted.

var testArray = [[true, "random-string", 3],
                 ["potato", false, 4],
                 [null, 4, 5]];

function twoDMap(twoDimensionalArray, transform) {
 return twoDimensionalArray.map(function (row, y) {
   var mapped = [];
   for (var x = 0; x < row.length; x++)
     mapped.push(transform(row[x], x, y));
   return mapped;
 })
}

twoDMap(testArray, function(cell, x, y) {
  console.log(cell, x, y);
})

if someone knows why the first attempt with the nested map didn't work, I'd be very curious to learn about that. I still don't know why it didn't have access to the scope that contained the y...

Upvotes: 1

Related Questions