Most efficient way to delete from array? - javascript

I have an array containing particles (fire, blood, smoke, etc.) in an HTML5 game. All particles have an expiry/lifespan. I'm creating up to 100 particles per frame at 60fps so I want to keep this array as clean as possible so I can loop through it efficiently.
I have heard it's better to use 'splice' rather than 'delete' to remove elements from an array. This makes sense to me as I'd rather not loop through keys of the array that are blank (that 'delete' leaves behind).
However, I tested this out and have a higher, more consistent frame rate if I 'delete' keys rather than splicing them to remove expired particles. The downside is the longer the game runs the longer my particles array gets.
Is there a better solution to this?

If the order of the items in the array doesn't matter, then simply assign the last item in the array to the one you want to overwrite and then delete it by reducing the .length.
function unordered_remove(arr, i) {
if (i <= 0 || i >= arr.length) {
return;
}
if (i < arr.length - 1) {
arr[i] = arr[arr.length-1];
}
arr.length -= 1;
}
This is much faster because it doesn't need to reindex and is good for situations where the order doesn't matter.

When you use delete on an array element, all you are actually doing is setting that array element to undefined. The array will still have the same length. When you use splice, you actually remove that element entirely. The element is removed, and everything after that element is shifted down 1 index.Of the two, delete is going to be faster since your array doesn't have to re-index.
As for performance, if leaving the deleted elements as undefined works, then that is probably the best way to do it. If you are concerned about the array length growing too long, or maybe have to search that array frequently and want to reduce overhead, you could periodically filter out the undefined elements like so:
function filterArr() {
myArr = myArr.filter(function(v) {
return typeof v !== 'undefined';
});
}
var interval = setInterval(filterArr, 5000);
This will give you the best of both worlds. When you need to remove the particles, you use delete to set the elements to undefined, which is faster than removing them in place. Every now and then, you remove them in place to keep your array size lower.
You could improve upon that depending on your requirements. Good luck :)

You'll have way higher performances by packing the array by yourself : less operations AND no need to dispose current array and create a new one (like Array.filter does), so much less garbage collection.
function packArray(tgtArray) {
if (!tgtArray || !tgtArray.length) return;
var srcIndex = 0;
var dstIndex = 0;
var arrayLength = tgtArray.length ;
do {
var currentItem = tgtArray[srcIndex];
if (currentItem.alive) {
if (srcIndex != dstIndex) {
tgtArray[dstIndex] = currentItem ;
}
dstIndex++;
}
srcIndex++;
} while (srcIndex != arrayLength) ;
dstIndex--;
tgtArray.length = dstIndex > 0 ? dstIndex : 0 ;
}

Related

Why using for is faster than some() or filter()

I tried two different way to do something and I am surprised by the performance result :
I have 2 versions of a function :
Using a for :
$scope.hasBlockResult = function (IS, area, block) {
if (!block)
return false;
for (var i = 0; i < $scope.filteredCartoList.length; i++) {
if ($scope.filteredCartoList[i].informationSystem === IS
&& $scope.filteredCartoList[i].area === area
&& $scope.filteredCartoList[i].block === block)
return true;
}
return false;
};
And using some() function :
$scope.hasBlockResult = function (IS, area, block) {
if (!block)
return false;
return ($scope.filteredCartoList.some(function (carto) {
if (carto.informationSystem === IS && carto.area === area && carto.block === block)
return true;
return false;
}));
};
Same thing here :
Between the for :
for (var i = 0; i < $scope.filteredCartoList.length; i++) {
if ($scope.filteredCartoList[i].informationSystem == IS
&& $scope.filteredCartoList[i].type != 'AM'
&& $scope.filteredCartoList[i].type != 'IF'
&& $scope.filteredCartoList[i].area == area
&& $scope.filteredCartoList[i].block == block)
$scope.resultList.push($scope.filteredCartoList[i]);
}
and the filter() :
$scope.resultList = $scope.filteredCartoList.filter(function (carto) {
if (carto.informationSystem == IS
&& carto.type != 'AM'
&& carto.type != 'IF'
&& carto.area == area
&& carto.block == block)
return true;
return false;
});
I expected the filter() and the some() methods to be faster than the for method, but in both case, according to angularjs batarang performance tab, the foris faster.
I took a look at the benchmarksyou posted in the comments. These benchmarks have a few flaws:
The loop example uses console.timeEnd and console.log within the benchmark itself, which are both slow. None of the other examples did this at time of writing.
The some example performs type coercion.
All of the tests are performing string concatenation within their loops.
In order to draw any conclusions from these benchmarks, we first need to eliminate these sources of bias.
Here are the results on an 8GB DDR3 i5 Laptop with these biases eliminated, re-ordered in terms of fastest to slowest (lower numbers are better):
OBJECT Average 0.0010666643114139636
SEEK Average 0.00593666957380871
LOOP Average 0.008436664550875625
SOME Average 0.013993332007279
FILTER Average 0.02592999837361276
These are what is to be expected, and here is why:
Object Access
Object access is very quick because objects are essentially hash maps. Regardless of the size of the object, accessing an element will be a constant speed.
Seek
Seek is implemented as using indexOf to locate an element and then accessing that element at the direct array index. While the actual method of doing this is implementation-specific, it is going to be very similar to object access and thus very fast.
Loop
The loop approach is slower primarily because unlike the 'seek' test, the loop test iterates over the entire array and does both array access AND object access. The seek method doesn't do this. It breaks out almost immediately after finding the element.
This means that in all except the worst cases, seek will be faster than loop.
Some
Some has the overhead of a function invocation to be invoked every single iteration. In addition, this cannot be optimized at all by the JIT compiler because the JIT compiler doesn't know what you're going to pass into some. Some will, at best, perform the same as loop in the most optimized cases but because of the function invocations it will always be slower.
Filter
Filter has all the caveats of "some", but it will always iterate over the entire array instead of halting at a single element. Due to this, you should always expect filter to be much slower than a for loop - especially when you consider that filter also creates a new array which it returns!
Nothing beats native (vanilla) javascript when it comes to performance. Question boils down to "Do you want to spend time and resources in re-inventing the wheel by doing it yourself or just leveraging an external lib that does it for you?". Yes you sacrifice load time and performance but you save time and time is money. You can make your for loop faster by also caching the length of the array as such
for (var i = 0, len = $scope.filteredCartoList.length; i < len; i++)
This is going to work faster especially in IE because here you are caching the length of your $scope.filteredCartoList instead of calculating it every iteration of the loop.
Consider these two examples:
for (var i = 0; i < array.length; i++) {
doThing(array[i]);
}
vs.
function processItem(item) {
doThing(item);
}
for (var i = 0; i < array.length; i++) {
processItem(array[i]);
}
This is basically the difference between the two. There also has to be some logic inside of filter and some for handling the return value from processItem but basically you're stacking a whole extra function call on top of your loop.

Remove element of array without splice()

I'm developing a JavaScript game and I want to keep my memory usage as low as possible.
Therefore I set some objects to null again, so they can get garbage collected.
I read an article which recommends avoiding functions like Array.splice(), because this creates a new array, which allocates new memory.
So I've implemented a JSFiddle with an own function, that deletes an element at a specific index and shifts all elements behind, so the length will be set to length -= 1. This only affects the existing array instead of creating a new one:
Function to use instead of splice:
deleteElem = function(arr, el) {
var index = arr.indexOf(el);
if (index > -1) {
var len = arr.length - 1;
for (var i = index; i < len; i++) {
arr[i] = arr[i + 1];
}
arr.length = len;
}
}
The JSFiddle for my function is sometimes faster, sometimes slower...
Should I pay more attention to better performance and worse memory, or better memory and worse performance?
What other ways exist to avoid using Array.splice?
You need to realize how jsperf runs your code. It doesn't run setup for each time it runs your code - the code runs hundreds or thousands of times for each setup.
That means you are using an empty array for 99.999999% of the calls and thus not measuring anything useful.
You could at least get some sense out of it by measuring it like this http://jsperf.com/splice-vs-own-function/2 however you need to note that the allocation of array of 50 times might blunt the differences and that your method is therefore actually much faster than the benchmark can show.

Convert for loop to a while loop in JavaScript

I've been trying to convert the following JS code that uses a for loop to a while loop and/or do-while loop.
var unique = function(array)
{
var newArray = []
array.sort()
for(var x in array) if(array[x] != array[x-1]) newArray.push(array[x])
return newArray
}
The code is suppose to return only distinct names from an array of names that repeat. I've been trying to convert the for loop but so far I've been running into problems using this:
do
{
newArray.push(array[x])
}
while(array[x] != array[x-1])
return newArray;
Can anyone help me? Thanks!
Is it guaranteed that your names are only going to be duplicated in sequence i.e. is it true that if a name does have a duplicate it will be directly after it? If not, then checking the elements that are directly next to each will not find all the duplicates. You'll have to do a nested for loop or some other n^2 algorithm.
var duplicated = false;
for (int x = 0; x < array.length; x++)
{
for (int y = 0; y < array.length; y++)
{
if (array[x] == array[y])
{
duplicated = true;
}
}
if (!duplicated)
{
array.push(array[x]);
}
duplicated = false;
}
return newArray;
please note, this implementation is very poor but it gets the point across.
You're very close. The following preserves the original sequence:
function getUnique(array) {
var newArray = array.slice(); // copy original
var i = newArray.length - 1;
do {
if (newArray[i] == newArray[--i]) {
newArray.splice(i, 1);
}
} while(i)
return newArray;
}
Note that the above assumes a sorted, contiguous array (no missing members). If you can't be sure of that, sort newArray before the do..while loop and maybe compact it to make it contiguous.
A while loop doesn't make sense for your use case. A while loop is good when you want to loop as long as some condition is met and then stop the first time it fails. But a for loop is good when you want to loop through a certain number of items (for instance all of them).
So stick with a for loop. Also a few notes on while loops for whenever you do use them:
A for loop automatically updates the loop index which in your case is x. A while loop doesn't. So, to replicate your for loop, you would need to manually increment x.
Also, you test at the top of the for loop. To mirror that behavior you would want a while instead of a do- while (while tests before executing each loop, do-while tests after).
But if you used a while loop, you'd exit the loop the first time array[x] != array[x-1] failed. And it sounds like you don't want that. You want to push all values that meet that test.

get sub array of array javascript

I have a array in javascript and I want a sub array of the array with element which are at position n*3, n=0,1,2.. for example if:
var arr = [1,2,3,4,5,6,7,8,9,10,11,12]
var subArr = [1,4,7,10]
Edit : any soln without looping.
Here's an example of a fancy way :
var brr = [1,2,3,4,5,6,7,8,9,10,11,12].filter(function(_,i){ return !(i%3) })
But a simple loop would have been as good (and would have been compatible with IE8). note that filter, even if it's not visible, does loop over the array. You can't avoid a loop (at least for an arbitrary sized array) even if you may disguise it.
Here's how you would do it with a standard loop :
var brr = [];
for (var i=0; i<arr.length; i+=3) brr.push(arr[i])
Performance is rarely a concern on such operations client side but you might find important to know that the for loop is much faster here : http://jsperf.com/looporfilter
In order to operate on a set of data of size n, m times, where m > 1, how would you avoid iteration? Really, there is no way unless you use a set of O(1) operations like this:
var arr = [1,2,3,4,5,6,7,8,9,10,11,12];
var subarr = [];
subarr.push(arr[0]);
subarr.push(arr[3]);
subarr.push(arr[6]);
subarr.push(arr[9]);
Here is a structural recursion (which can be represented by a loop, and does technically loop).
var arr = [1,2,3,4,5,6,7,8,9,10,11,12];
var subarr = [];
(function recur(n){
if( n >= arr.length ) return;
subarr.push(arr[n]);
recur(n+3);
})(0);
To note: a straight for loop will always be faster. In an expansion of #dystroy's jsperf, this recursion ran slower than the for loop, but faster than the filter. http://jsperf.com/looporfilter/2
just for kicks, i searched for a way to actually do it without a loop like the OP wanted.
without using a loop, it's tough.
the closest i could manage gets the right numbers, but converts them to Strings instead of numbers.
var r=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
alert( "".replace.call(r+",-0,-0,-0", /(\d+),\d+,?(\d+,|$)/g, "$1,")
.replace(/(,?\-0){1,4}$/g,"")
.split(",") );
//shows: 1,4,7,10,13
if you need strong numbers, it's easy, but i am not sure if adding .map(Number) after .split(",") would constitute a loop in your book, but this is the only version that actually finds the desired results without a loop.
this also only works on positive integers as coded.
again, more for fun than something i would recommend using; don't be afraid of loops...
Here's a solution with no loop:
var arr = [1,2,3,4,5,6,7,8,9,10,11,12];
// pickInterval is a function that automatically picks every "n"
// elements from an array, starting with the first element
var subArr = pickInterval( arr, 3 );
// now subArr is [1,4,7,10]
Simple, isn't it? And not a loop in sight.
Ah, but you ask, "What's the catch? You haven't implemented that pickInterval() function, have you? I bet there's a loop in it."
And you're right. Even if we write our pickInterval() function using some other function that doesn't look like a loop, that function will have a loop in it. And if that one just calls yet another function, eventually you will find a loop.
It may be turtles all the way down, but there's a loop underneath them all.
So, to implement pickInterval(), we could use either approach from #dystroy's answer. We can put the loop right inside the function:
function pickInterval( array, interval ) {
var result = [];
for( var i = 0, n = array.length; i < n; i += 3 )
result.push( array[i] );
return result;
}
Or we can use .filter():
function pickInterval( array, interval ) {
return array.filter( function( _, i ){
return !( i % 3 );
});
}
Of course there's still a loop here, down inside .filter(). It's just been hidden from us in the very same way that pickInterval() hides any loop from its caller.
And that's the real point. Use .filter(), use a for loop, use whatever you want, as long as you encapsulate it inside a function. Then the inner workings of that function don't matter so much. If you like the for loop because it's fast and easy to understand, use that. If you like the filter() version because it's interesting and elegant, use that. If you later need to use it on a very large array and it's running slow, you can replace it with the for loop version without affecting the code that uses pickInterval().
Whatever code you write for this or for something like it, don't put it inline. Make a function.

Optimise a simple double for loop for IE

array.sort(function(left, right) {
return index(otherArray, left) < index(otherArray, right);
});
This is O(len(array) ^ 2) so for a reasonable size array of len = 1000 this takes constant * 1 million operations which easily overshoots the IE 5 million operators cap.
Thus IE throws a script is taking too long even though this is fast.
The problem is that IE does not have it's own Array.prototype.indexOf so I can't reduce the operation count down to O(len(array) and rely instead end up using a double for loop instead of a single for loop.
I considered array.join and using String.prototype.indexOf but the objects in the arrays are DOM elements and you can't convert them to a string (easily).
Telling IE users to remove this default cap is not an option.
I can think of two possible solutions to this problem: one of which will work everywhere, the other which is entirely IE-proprietary (and I expect doesn't work in IE9, but that supports Array.prototype.indexOf, so that's a non-issue).
The first, simpler, solution is to just set a property on each HTMLElement of the desired order and sort by that. If you care about the desired order persisting, you'll have to make sure the HTMLElement objects don't get garbage collected, so you'll have to keep references to them around (it's probably simplest to just create an array in the global scope for it).
The IE-only solution is to do something similar to what #maclema was proposing, using a lookup object, and HTMLElement.uniqueID:
var otherArrayLookup = {};
for (var i=0; i < otherArray.length; i++) {
otherArrayLookup[otherArray[i].uniqueID] = i;
}
array.sort(function(left, right) {
return otherArrayLookup[left.uniqueID] < otherArrayLookup[right.uniqueID];
});
You'll want to add some branches in there (don't put any within the callback function, but use different callback functions) for the Array.prototype.indexOf supported case, the HTMLElement.uniqueID supported case, and the none-of-the-above case.
You could try making an index lookup object. This should greatly increase performance too.
var otherArrayLookup = {};
for ( var i=0; i<otherArray.length; i++ ) {
otherArrayLookup[otherArray[i]] = i;
}
array.sort(function(left, right) {
return otherArrayLookup[left] < otherArrayLookup[right];
});

Categories

Resources