I'm trying to use reduce() combine a set of arrays in a "collated" order so items with similar indexes are together. For example:

input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]

output = [ 'first','1','uno','one','second','2','dos','two','third','3','three','4' ]

It doesn't matter what order the items with similar index go as long as they are together, so a result of 'one','uno','1'... is a good as what's above. I would like to do it just using immutable variables if possible.

I have a way that works:

	const output = input.reduce((accumulator, currentArray, arrayIndex)=>{
        currentArray.forEach((item,itemIndex)=>{
            const newIndex = itemIndex*(arrayIndex+1);
            accumulator.splice(newIndex<accumulator.length?newIndex:accumulator.length,0,item);
        })
        return accumulator;
    })

But it's not very pretty and I don't like it, especially because of the way it mutates the accumulator in the forEach method. I feel there must be a more elegant method.

I can't believe no one has asked this before but I've tried a bunch of different queries and can't find it, so kindly tell me if it's there and I missed it. Is there a better way?

To clarify per question in comments, I would like to be able to do this without mutating any variables or arrays as I'm doing with the accumulator.splice and to only use functional methods such as .map, or .reduce not a mutating loop like a .forEach.

Maybe just a simple for... i loop that checks each array for an item in position i

<!-- begin snippet: js hide: false console: true babel: false --> <!-- language: lang-js -->
var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]]

var output = []
var maxLen = Math.max(...input.map(arr => arr.length));

for (i=0; i < maxLen; i++) {
  input.forEach(arr => { if (arr[i] !== undefined) output.push(arr[i]) })
}

console.log(output)
<!-- end snippet -->

Simple, but predictable and readable


Avoiding For Each Loop

If you need to avoid forEach, here's a similar approach where you could: get the max child array length, build a range of integers that would've been created by the for loop ([1,2,3,4]), map each value to pivot the arrays, flatten the multi-dimensional array, and then filter out the empty cells.

First in discrete steps, and then as a one liner:

<!-- language: lang-js -->
var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]];

Multiple Steps:

<!-- language: lang-js -->
var maxLen = Math.max(...input.map(arr => arr.length));
var indexes = Array(maxLen).fill().map((_,i) => i);
var pivoted = indexes.map(i => input.map(arr => arr[i] ));
var flattened = pivoted.flat().filter(el => el !== undefined);

One Liner:

<!-- language: lang-js -->
var output = Array(Math.max(...input.map(arr => arr.length))).fill().map((_,i) => i)
               .map(i => input.map(arr => arr[i] ))
               .flat().filter(el => el !== undefined)