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
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)
Simple, but predictable and readable
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:
var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]];
Multiple Steps:
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:
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)
Use Array.from()
to create a new array with the length of the longest sub array. To get the length of the longest sub array, get an array of the lengths with Array.map()
and take the max item.
In the callback of Array.from()
use Array.reduceRight()
or Array.reduce()
(depending on the order you want) to collect items from each sub array. Take the item if the current index exists in the sub array. Flatten the sub arrays with Array.flat()
.
const input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]] const result = Array.from( { length: Math.max(...input.map(o => o.length)) }, (_, i) => input.reduceRight((r, o) => i < o.length ? [...r, o[i]] : r , []) ) .flat(); console.log(result);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With