First: Since you already know how many chunks you want, there's no point in checking if the chunk exists and otherwise declare it. In general terms, you should not need to check for objects you can define deterministically.
So instead of doing this check on each loop:
if (typeof result[i] == 'undefined'){
result[i] = [];
}
create an array of N empty arrays beforehand.
const result = Array.from(Array(chunks),item=>[]);
Second: Albeit the performance difference is negligible, checking for i
's value and conditionally reassigning it is less efficient than using the modulo operator on its value regardless
So instead of
results[i].push(...)
i++
i = (i == chunks) ? 0 : i;
You can do
results[i % chunks].push(...)
i++
With the above, your function could be expressed as
function usingShift(myArray, chunks=5){
const copiedArray = myArray.slice(),
result=Array.from(Array(chunks),item=>[]);
let i=0;
while(copiedArray.length){
result[i % chunks].push(copiedArray.shift());
i++;
}
return result;
}
Third: As you've been told, shifting from an array is expensive. I understand you're doing it because you want to populate the chunks in the same order of the original array. However you can achieve the same popping from the a reversed array:
If you declare
const a = myArray.slice().reverse();
You can replace the usage of shift with
result[i].push(a.pop());
The function would be something like:
function usingPop(myArray, chunks=5){
const reversedArr = myArray.slice().reverse(),
result=Array.from(Array(chunks),item=>[]);
let i=0;
while(reversedArr.length){
result[i % chunks].push(reversedArr.pop());
i++;
}
return result;
}
However... you'd still be copying the array and performing a mutation on the copy. @Miklós Mátyás solution has the advantage of populating the result without copying nor extracting items from the source array. Now, you haven't said the source array will be always the same (9 elements from 1 to 9). It could as well have repeated/unsorted items, so his solution should take into account not the item itself but its index, which can be expressed as:
function filterByModulo(myArray, chunks=5){
return Array.from(Array(chunks),(_,modulo)=>{
return myArray.filter((item,index) => index % chunks === modulo);
});
}
That's pretty clean, but it's filtering on the original array as many times as chunks you want, so it's performance degrades according to the source array length AND the chunk quantity.
Personally I believe this is a case in which reduce
would be more appropiate and pretty concise, while avoiding the copying or mutation of the source array.
function usingReduce(myArray,chunks=5) {
const result=Array.from(Array(chunks),i=>[]);
return myArray.reduce( (accum,item,index)=>{
accum[index%chunks].push(item);
return accum;
}, result);
}
Finally there's the classic for
loop
function classicFor(sourceArr, chunks=5) {
const lengthOfArray=sourceArr.length;
const result=Array.from(Array(chunks),i=>[]);
for(let index=0; index<lengthOfArray ; index++) {
result[index % chunks ].push(sourceArr[index]);
}
return result;
}
I made a test case at JSPerf in which it shows that the for loop is the most efficient. (I threw in forEach
and for..of
implementations too).
Running with a source array of 5000 items and 5 chunks shows that using pop on the source is more efficient than using shift by a 2.89x factor. It even looks more efficient that reduce. The classic for loop is the fastest whereas filtering N times comes up last by a ratio of 9x the modulo filtering.
If you use a source of 100000 items and 15 chunks the classic for is still the most efficient (still 9x modulo filtering) but the other implementations do scale a bit better than modulo filtering.