2

For example I have two very big arrays a and b, each has millions of elements. I want to append all the elements of the array b to array a. Also I don't want to create a new array, but alter existing array a (so concat is not an option).

I have tried:

Array.prototype.push.apply(a, b)

But for very big arrays this gives me an error:

RangeError: Maximum call stack size exceeded

I know that I can make a loop and add elements one by one with push.

Is there a better way?

Luka
  • 2,779
  • 3
  • 17
  • 32
  • 2
    What's the nature of these bazillion elements? Strings? Numbers? Nodes? Object literals? Arrays? Array of arrays? Arrays of objects? etc. – zer00ne Mar 25 '17 at 22:53
  • @zer00ne I really think this is irrelevant, but if you want to know those are objects. – Luka Mar 25 '17 at 23:11
  • @Luka It's highly relevant. Why are you handling so many in the first place? What's the purpose? It's just way too much to handle. – Andrew Li Mar 25 '17 at 23:12
  • @Luka Please be specific is to what type of object, because everything is an object. In fact will you be so kind as to post a small sample of the gazillion elements you intend to tame. – zer00ne Mar 25 '17 at 23:15
  • you can "duff's device" the loop and push many elements per loop, up to args limit... – dandavis Mar 25 '17 at 23:41
  • Perhaps think about how you can categorise the values into multiple arrays – DeclanPossnett Mar 25 '17 at 23:46
  • In JS there is no secure way of passing indefinitely many arguments. So the `...` operator or ES5 equivalent `.apply` trick is no way out. The only thing you can do is `b.forEach(e => a.push(e))` unles you want to go for a `for` or `while` loop. – Redu Mar 26 '17 at 09:04

2 Answers2

1

Judging by this question (Is there a max number of arguments JavaScript functions can accept?), the maximum number of arguments you could safely append at one time would be about 100,000 say. Assuming you have the memory to have a duplicate list of 100,000 items at a time you could use the slicing method displayed below. (See the Splice and Apply methods detailed below)

However, I added benchmarking comparisons to the individual push method in different forms to see if there's any performance benefit in doing this since under the hood it's likely the splice concatenation is still element-wise rather than a bulk memcpy operation.

const x = [];

for (let i = 0; i < 10000000; i += 1) {
    x.push(i);
}

const a = x.slice();
const b = x.slice();
const v = x.slice();
const y = x.slice();
const z = x.slice();


// append 100,000 at a time using splice
const sliceStart = new Date();
for (let i = 0; i < b.length; i += 100000) {
    const len = (i + 100000) % (b.length + 1);
    const c = b.slice(i, len);
    a.splice(a.length, 0, ...c);
}
const sliceEnd = new Date();

// append 100,000 using Array.prototype.push
const protoStart = new Date();
for (let i = 0; i < b.length; i += 100000) {
    const len = (i + 100000) % (b.length + 1);
    const c = b.slice(i, len);
    Array.prototype.push.apply(v, c);
}
const protoEnd = new Date();

// using for and push
const pushStart = new Date();
for (let i = 0; i < b.length; i += 1) {
    y.push(b[i]);
}
const pushEnd = new Date();

// using for and push
const batchPushStart = new Date();
for (let i = 0; i < b.length; i += 8) {
    y.push(b[i]);
    y.push(b[i + 1]);
    y.push(b[i + 2]);
    y.push(b[i + 3]);
    y.push(b[i + 4]);
    y.push(b[i + 5]);
    y.push(b[i + 6]);
    y.push(b[i + 7]);
}
const batchPushEnd = new Date();

// using forEach and push
const forEachStart = new Date();
b.forEach(i => z.push(i));
const forEachEnd = new Date();

console.log("Slice method:", sliceEnd - sliceStart);
console.log("Apply method:", protoEnd - protoStart);
console.log("For and push method:", pushEnd - pushStart);
console.log("For and batch push method:", batchPushEnd - batchPushStart);
console.log("Foreach and push method:", forEachEnd - forEachStart);

Run with 10,000,000 elements results on a 2014 MacBook Pro 15":

Slice method: 1400
Apply method: 275
For and push method: 896
For and batch push method: 409
Foreach and push method: 707

The for and foreach single-push methods are roughly equivalent in terms of performance on Chrome's V8. The slice method I'm seeing around 2-3x worse performance on.

Update: After adding the a batching method for Array.prototype.push.apply it gives even better performance than the single-push methods! Unrolling the loop however seems to sometimes have a significant performance improvement and sometimes not, depending on the size of the list and other work being before or after it...?

Note that increasing the size of the initial x array can crash the page in chrome because it can exceed the max memory limit for a page/tab.

To summarize, stick with a regular array.push(..) for simplicity, but the batched Array.prototype.push.apply method might be of interest for performance.

Community
  • 1
  • 1
Liam Gray
  • 1,089
  • 9
  • 16
0

There are no options since you have a maximum arguments length that you can pass to a function (not standard defined);

Options are (besides the .apply):

1- Spread operator and .call. Same error.

Array.prototype.push.call(a, ...b);

2- Splice (by Andrew). Same error.

a.splice(a.length, 0, ...b); 

Since the limit on arguments, you can't achieve this without iterating individually, as concat does.

Community
  • 1
  • 1
Edmundo Santos
  • 8,006
  • 3
  • 28
  • 38
  • Just for show another way to do the same thing, even if it fails in the same way. I don't know why you deleted your answer, is always good to know that are more ways to do something. – Edmundo Santos Mar 25 '17 at 22:53
  • Agree. But in this case that isn't possible to achieve the solution, is good to know why. Maybe it can fail only with .apply and .call, but as your answer showed, the problem still happens using splice, confirming that the problem is at the arguments length. – Edmundo Santos Mar 25 '17 at 22:56
  • Yes, it is. http://codepen.io/anon/pen/PpBjGa (change the call_stack_error to true). – Edmundo Santos Mar 25 '17 at 23:00
  • I stand corrected, I was confused on two different things. Sorry about that. I still think you should at least change the wording "options are...". – Andrew Li Mar 25 '17 at 23:15