1

I have a multidimensional array, which I don't know its size, what I do know is that each child will have the same length and same structure.

I need to concat the inner arrays of the first child with the inner arrays of its siblings so all the children same index inner arrays are in one array.

(If someone wants to phrase it a little bit better, or improve the title be my guest)

Example:

let arrays = [

    //1
    [[1, 2], [3, 4], [5, 6]],

    //2
    [[10, 20], [30, 40], [50, 60]],

    //3
    [[100, 200], [300, 400], [500, 600]],

    //N
    [[10000, 20000], [30000, 40000], [50000, 60000]],

];

Expected result:

[
    [1, 2, 10, 20, 100, 200, 10000, 20000],
    [3, 4, 30, 40, 300, 400, 30000, 40000],
    [5, 6, 50, 60, 500, 600, 50000, 60000]
]

Here is what I'm currently doing, which is working, but it's an overkill.

/**
*   Helper function
*   Will return all the values at ${index} of each array in ${arrays}
*   Example: index(1, [[1,2], [3,4]]); //[2, 4]
*/

function index(index, arrays){

    let results = [];

    for(let i = 0, len = arrays.length; i < len; i++)
        results.push(arrays[i][index]);

    return results;
}

let first = arrays.shift();    
let output = first.map((item, i) => item.concat( ...index(i, arrays) ))

I'm looking for a more efficient way to do this, since it's running in a node server and also a more clever way (which doesn't have to be more efficient).

Note: I'm using node v7.8.0 so ES6 can be used.

UPDATE:

The spread operator is significantly slower than apply

So my code is much faster this way:

return first.map((item, i) => [].concat.apply(item, index(i, clone) ));

JSPERF:

I tested all the answers in this jsperf and @ibrahimMahrir way is clearly the fastest.

/**
*   Will return all the values at ${index} of each array in ${arrays}
*   Example: index(1, [[1,2], [3,4]]); //[2, 4]
*/

function index(index, arrays){

    let results = [];

    for(let i = 0, len = arrays.length; i < len; i++)
        results.push(arrays[i][index]);

    return results;
}

let arrays = [

 //1
 [[1, 2], [3, 4], [5, 6]],

 //2
 [[10, 20], [30, 40], [50, 60]],

 //3
 [[100, 200], [300, 400], [500, 600]],

 //N
 [[10000, 20000], [30000, 40000], [50000, 60000]],

];

let first = arrays.shift();
let output = first.map((item, i) => item.concat( ...index(i, arrays) ));

console.log(output);
Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98

3 Answers3

2

This is a typical reducing job with mapping. You may do as follows;

var arrs = [[[1, 2], [3, 4], [5, 6]], [[10, 20], [30, 40], [50, 60]], [[100, 200], [300, 400], [500, 600]], [[10000, 20000], [30000, 40000], [50000, 60000]]],
    res  = arrs.reduce((p,c) => p.map((s,i) => s.concat(c[i])));
console.log(res);
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    OP is looking for performance enhancment so the use of `reduce` and other similar functions will be costly. – ibrahim mahrir Apr 26 '17 at 01:39
  • 1
    @Marcos Casagrande You might improve the performance of the above snippet just by rephrasing it into an imperative style. Then it will run a tiny little bit faster for the cost of an uglier look :) – Redu Apr 26 '17 at 01:44
  • 1
    @Redu I also wanted a clever way, this is really more elegant that my current way. I have to admit that is late here and I have been working for 12 hours and couldn't think of a better way in my state of mind :P – Marcos Casagrande Apr 26 '17 at 01:56
1

Using reduce and its sisters come at a cost in performance. Using basic for loops is not the prettiest you get, but surely is faster.

function group(arrays){

    let results = [],
        len = arrays.length;

    if(!len) return results; // if this line is not necessary then remove it

    let i, j, k,
        innerLen = arrays[0].length;

    for(j = 0; j < innerLen; j++) {
        let arr = [];
        for(i = 0; i < len; i++)
            for(k = 0; k < arrays[i][j].length; k++) // assuming the last level of arrays could be of different lengths
                arr.push(arrays[i][j][k]);
        results.push(arr);
    }

    return results;
}

let arrays = [[[1,2],[3,4],[5,6]],[[10,20],[30,40],[50,60]],[[100,200],[300,400],[500,600]],[[10000,20000],[30000,40000],[50000,60000]]];
console.log(group(arrays));
ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
1

Here is my attempt at a readable approach, based on decomposing the operation using zip and flatten utility methods:

let arrays = [
    //1
    [[1, 2], [3, 4], [5, 6]],
    //2
    [[10, 20], [30, 40], [50, 60]],
    //3
    [[100, 200], [300, 400], [500, 600]],
    //N
    [[10000, 20000], [30000, 40000], [50000, 60000]],
]

// from http://stackoverflow.com/a/10284006/7256039
const zip = a => a[0].map((_,i) => a.map(e => e[i]))

const flatten = a => [].concat(...a)

let result = zip(arrays).map(flatten)

console.log(result)
.as-console-wrapper { min-height: 100%; }

Here is the fastest inline/for-loop version I can come up with while still not sacrificing too much readability:

let arrays = [
    //1
    [[1, 2], [3, 4], [5, 6]],
    //2
    [[10, 20], [30, 40], [50, 60]],
    //3
    [[100, 200], [300, 400], [500, 600]],
    //N
    [[10000, 20000], [30000, 40000], [50000, 60000]],
]

function zipMapFlatten (a) {
  const result = [], rows = a.length, cols = a[0].length
  let i, j, e
  for (j = 0; j < cols; j++) {
    result.push(e = [])
    for (i = 0; i < rows; i++) e.push.apply(e, a[i][j])
  }
  return result
}

let result = zipMapFlatten(arrays)

console.log(result)
.as-console-wrapper { min-height: 100%; }
gyre
  • 16,369
  • 3
  • 37
  • 47