3

I am looking for the best way to replace or add to elements of an array without deleting the original reference. Here is the set up:

var a = [], b = [], c, i, obj;
for ( i = 0; i < 100000; i++ ) { a[ i ] = i; b[ i ] = 10000 - i; }
obj.data_list = a;

Now we want to concatenate b INTO a without changing the reference to a, since it is used in obj.data_list. Here is one method:

for ( i = 0; i < b.length; i++ ) { a.push( b[ i ] ); }

This seems to be a somewhat terser and 8x (on V8) faster method:

a.splice.apply( a, [ a.length, 0 ].concat( b ) );

I have found this useful when iterating over an "in-place" array and don't want to touch the elements as I go (a good practice). I start a new array (let's call it keep_list) with the initial arguments and then add the elements I wish to retain. Finally I use this apply method to quickly replace the truncated array:

var keep_list = [ 0, 0 ];
for ( i = 0; i < a.length; i++ ){ 
  if ( some_condition ){ keep_list.push( a[ i ] );
}

// truncate array
a.length = 0;

// And replace contents
a.splice.apply( a, keep_list );

There are a few problems with this solution:

  • there is a max call stack size limit of around 50k on V8
  • I have not tested on other JS engines yet.
  • This solution is a bit cryptic

Has anyone found a better way?

Michael Mikowski
  • 1,269
  • 1
  • 10
  • 21
  • Setting `arr.length = 0` will keep the instance itself alive whilst removing all items in the _Array_. – Paul S. Oct 30 '13 at 02:50
  • `Let's say you have an array that`, then what ? – The Alpha Oct 30 '13 at 02:55
  • There's a great website for performance testing: jsperf.com – SheetJS Oct 30 '13 at 02:59
  • Ok, the question is complete. This is a method I dug up to concatenate arrays or replace 'in-place'. While it's not really posted as a question, I would certainly appreciate feedback. For instance, it may work horribly on IE or Firefox, or there may be a better way. In any event, I thought it clever enough to share. – Michael Mikowski Oct 30 '13 at 02:59
  • @MichaelMikowski StackOverflow is designed for question-answer style items, and you can answer your own questions. If you think the community would benefit from this information, why not structure your data such? As for feedback, don't forget that `push` can take multiple args too, and that you can have `RangeError: Maximum call stack size exceeded` by passing **too many args** into functions (as you're talking about large numbers). – Paul S. Oct 30 '13 at 03:20
  • @paulS. I did note that over 50K the max call size does get exceeded on V8. But good point about using push with apply - that might be cleaner and faster. However, it will still suffer from the max call size. Luckily, the stacks I am dealing with are much smaller. I guess I should ask: Is there a better way to do this? – Michael Mikowski Oct 30 '13 at 03:27
  • If you're trying to get a better way of doing something, it might be a good idea to post your issue with your current code **only**, and explain the problem with it more explicitly - right now it might not be 100% obvious what you're asking from a quick glance. – Qantas 94 Heavy Oct 30 '13 at 03:36
  • hopefully you can also explain why you need to do this. You've explored a few options, but it feels very much like you're trying to solve a symptom caused by a decision you made, rather than trying to solve the problem that you invented this solution for. – Mike 'Pomax' Kamermans Oct 30 '13 at 04:00
  • here is a good answer to this: http://stackoverflow.com/questions/351409/appending-to-array/ – technosaurus Oct 30 '13 at 04:04
  • @technosaurus Thanks. It seems like a.push.apply( a, [ n0, n1, n2 ... nn ] ) might the best solution. It should certainly be less cryptic than the splice, although I will profile it just to make sure. – Michael Mikowski Oct 30 '13 at 04:59
  • @PaulS. See my answer for a way to avoid the maximum stack error. Also, what do you think about the **wrapper** approach? – plalx Oct 30 '13 at 13:23

1 Answers1

1

Probably that the most comprehensive way yet still efficient would be to use push.

var source = [],
    newItems = [1, 2, 3];

source.push.apply(source, newItems);

If you ever reach the maximum call stack, you could seperate the operation into multiple batches.

var source = [],
    newItems = new Array(500000),
    i = 0,
    len = newItems.length,
    batch;

for (; i < len; i++) newItems[i] = i;

//You need to find a real cross-browser stack size, here I just used 50k
while((batch = newItems.splice(0, 50000)).length) {
    source.push.apply(source, batch);
}

console.log(source[499999]);

Also keep in mind that expensive operations might hang the browser, especially in old browsers that have slow JS engines. To circumvent the issue, you could further split the process into smaller batches and let the browser breath by using setTimeout.

Finally another approach I thought of would be to use a wrapper object around your array which would allow you to replace the array directly since your references would be retained through the object.

var arrWrapper = { list: [] },
    obj1 = { items: arrWrapper },
    obj2 = { items: arrWrapper };

//update the array
obj2.items.list = [1, 2, 3, 4];

//access the array
obj1.items.list;

The only restriction would be to avoid keeping a reference to arrWrapper.list directly.

Note: If you are targetting modern browsers only, you could probably make use of WebWorkers. However as far as I know, you can only pass serialized data which means that the worker wouldn't be able to modify the source array directly.

plalx
  • 42,889
  • 6
  • 74
  • 90
  • Works well +1. The way the question was posed was as if OP didn't want to actually refactor code anywhere else, meaning I'm not sure if this will suit the "situation". If it was a modern browser, surely a getter and setter would be cleaner than a webworker? – Paul S. Oct 30 '13 at 14:07
  • @PaulS. Could you explain how custom getters/setters would help? I am probably missing something, but I tried to think about it and haven't found a better solution involving them. – plalx Oct 30 '13 at 19:39
  • `Object.defineProperty(obj, 'data_list', {get: function () {return a;});`, then assuming you're modifying `a = []` in the same closure later, the getter updates with the change. – Paul S. Oct 30 '13 at 19:50
  • To avoid call-stack overflow and avoid per-browser-limit-hell, one might just start with a reasonable batch size (say 50k), and then use try-catch block with interpolation to determine max-per-browser stack size. The value may be calculated once and stored in a static variable. The wrapper approach tries to avoid the problem altogether, but the array still gets clobbered, which is simply unacceptable in many situations. Example: obj = { list : [ 1,2,3] }; a = obj.list; obj.list = [3,4,5]; b = obj.list; Notice a !== b. Only the key remains the same. – Michael Mikowski Oct 31 '13 at 01:44
  • @MichaelMikowski Well, it's because you should treat the array wrapper as if it was the array reference. If you never store the array directly, you will never have that issue. That's a design decision that needs to be enforced in code. However, to avoid multiple property lookups, there's nothing preventing you from storing the *real* array in a local reference for quick lookups. – plalx Oct 31 '13 at 01:51