1

I found this bit of code to transpose an array on another post, but I don't understand why we're only acting on the first element of the array a[0]. Also, what does the underscore perform? (When I google underscore, all I get is the underscore library).

function transpose(a)
{
  return a[0].map(function (_, c) { return a.map(function (r) { return r[c]; }); });
}
nem035
  • 34,790
  • 6
  • 87
  • 99
TheRealFakeNews
  • 7,512
  • 16
  • 73
  • 114

3 Answers3

0

The code does indeed transpose a matrix (2d array).

The outer map call operates on a[0] in order to get column indexes and to generate an output array that's "columns"-long (if the input is amn, it returns a new array with n entries). The first parameter isn't used so a placeholder identifier is used (_). The second parameter (the column index) is used in the inner map to access the correct cell.

Within that call, another map call is made, this time on the entire array, which effectively maps the rows. Each row (an array of cells) is turned to single value r[c], and together these generate the output array that's returned to the outer map.

Amit
  • 45,440
  • 9
  • 78
  • 110
0

Let's expand the code for better readability and walk through it:

// Pass an array to transpose: i.e. transpose(myArray);
function transpose(a){

  // return the array returned from calling the .map method
  // on the first item in the array (this assumes that the first
  // item in the array is, itself, an array:
  return a[0].map(
                   // Provide the function for the map to use
                   // This function can take 3 arguments for:
                   // currentValue, index, array
                   // The code below would access currentValue with _
                   // and index with c 
                   function (_, c) { 
                      // The mapping callback function's job is to return
                      // values that will be placed into a new array that
                      // the overall .map method creates. Here, the callback
                      // is calling .map on the original array:
                      return a.map( 
                          // This callback is using the variable r to 
                          // receive the currentValue of the array element
                          // being looped over (which is assumed to be an
                          // array itself here: 
                          function(r) {
                              // This function returns elemeent at index
                              // position: c from the array element r and
                              // adds this to the new array being created
                              // by the nested .map call
                              return r[c]; 
                          });
                   });
}

Now, the argument names of: _, c and r are simply chosen by the author, but with .map(), it's best to use val, index and arr (or names very similar) to keep in mind what the .map() callback function's arguments represent.

This function works on the first element of the passed in array (a), which is a[0] and is, itself, an array.

It then moves through the elements of the original array, grabbing the elements at the index position corresponding to the first array, but elements from the second.

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • If the function takes 3 arguments, why are only 2 supplied and how does js know that `_` and `c` are current value and index, as opposed to another combination of the three parameters such as index, and array? – TheRealFakeNews Mar 31 '16 at 22:43
  • 1
    In JavaScript, all arguments are optional. If a function is set up to take 3 arguments and only two are passed, then the function uses the ones that were passed and the 3rd argument gets a value of `undefined` because no value was passed for it. As with all parameters, you can call them what you like, but given that `.map()`'s callback is automatically called by the JS runtime, it controls what arguments are passed into it and map WILL BE passed 3 arguments: currentValue, indexPosition, actualArray. Your code just captures the first two under the argument names `_` and `c`. – Scott Marcus Mar 31 '16 at 22:46
  • 1
    It's not that only 2 are supplied, it's that 2 are only being captured. The JS runtime is what calls your `.map()` callback and passes the arguments to it. The callback you write can decide with of the 3 it wishes to capture. This is true throughout JavaScript. – Scott Marcus Mar 31 '16 at 22:47
0

what does the underscore perform?

The reason first argument to map() is named _ is because its not used. An underscore is usually picked by convention as a name for unused arguments.

but I don't understand why we're only acting on the first element of the array a[0]

Using a[0] is arbitrary. Since all rows in a matrix have equal length, any index would work. We are doing this to obtain the number of columns.

Essentially, the first map() iterates over the first row (all columns) and on each iteration, ignores the current column value and grabs the current column index. In other words, the first map() iterates over the "width" of the matrix, from left to right, grabbing the current column index each time.

The second map() is inside the first map(). This means that it, for every column index, iterates over all the rows (height of the matrix) and on each iteration, creates a new row from the current row using the corresponding column index.

  • Important thing to note is that map creates a new array each time so you are creating a new transposed matrix, not changing the initial one in any way.

Visual Example

If you start with the matrix:

[ ['a1', 'a2', 'a3'] ]
[ ['b1', 'b2', 'b3'] ]

Then on each step, here's what is happening (using M for the original matrix and T for the transposed matrix):

// Step1
columnIndex = 0 
T[columnIndex][0] becomes M[0][columnIndex] which is "a1"
T[columnIndex][1] becomes M[1][columnIndex] which is "b1"
transposed row 0 becomes ["a1", "b1"]

// Step2
columnIndex = 1 
T[columnIndex][0] becomes M[0][columnIndex] which is "a2"
T[columnIndex][1] becomes M[1][columnIndex] which is "b2"
transposed row 1 becomes ["a2", "b2"]

// Step3
columnIndex = 2 
T[columnIndex][0] becomes M[0][columnIndex] which is "a3"
T[columnIndex][1] becomes M[1][columnIndex] which is "b3"
transposed row 2 becomes ["a3", "b3"]

And you end up with a transposed matrix:

[ ['a1', 'b1'] ]
[ ['a2', 'b2'] ]
[ ['a3', 'b3'] ]

Here's code with altered formatting and variable names to show more clearly what is happening. Open the console to see what is happening in each step.

var matrix = [];
matrix.push(['a1', 'a2', 'a3']); // first row
matrix.push(['b1', 'b2', 'b3']);

/*
  matrix = 
  [ ['a1', 'a2', 'a3'] ]
  [ ['b1', 'b2', 'b3'] ]
*/

function transpose(matrix) {
  var firstRow = matrix[0];

  var transposedMatrix = firstRow.map(function(UNUSED, columnIndex) {

    console.debug('\ncolumnIndex = %d', columnIndex);

    var transposedRow = matrix.map(function(row, idx) { // <-- idx is only used for logging, it's not necessary

      console.debug('T[%d][%d] = becomes %o', columnIndex, idx, row[columnIndex]);
      return row[columnIndex];

    });

    console.debug('transposed row %d becomes %o: ', columnIndex, transposedRow);

    return transposedRow;
  });

  return transposedMatrix;
}


var transposed = transpose(matrix);

/*
  transposed = 
  [ ['a1', 'b1'] ]
  [ ['a2', 'b2'] ]
  [ ['a3', 'b3'] ]
*/

console.dir(transposed);
Community
  • 1
  • 1
nem035
  • 34,790
  • 6
  • 87
  • 99