Optimise a simple double for loop for IE - javascript

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];
});

Related

Performance of array includes vs mapping to an Object and accessing it in JavaScript

According to the fundamentals of CS
the search functionality of an unsorted list has to occur in O(n) time where as direct access into an array will occur in O(1) time for HashMaps.
So is it more performant to map an array into a dictionary and then access the element directly or should I just use includes? This question is specifically for JavaScript because I believe this would come down to core implementation details of how includes() and {} is implemented.
let y = [1,2,3,4,5]
y.includes(3)
or...
let y = {
1: true,
2: true
3: true
4: true
5: true
}
5 in y
It's true that object lookup occurs in constant time - O(1) - so using object properties instead of an array is one option, but if you're just trying to check whether a value is included in a collection, it would be more appropriate to use a Set, which is a (generally unordered) collection of values, which can also be looked up in linear time. (Using a plain object instead would require you to have values in addition to your keys, which you don't care about - so, use a Set instead.)
const set = new Set(['foo', 'bar']);
console.log(set.has('foo'));
console.log(set.has('baz'));
This will be useful when you have to look up multiple values for the same Set. But, adding items to the Set (just like adding properties to an object) is O(N), so if you're just going to look up a single value, once, there's no benefit to this nor the object technique, and you may as well just use an array includes test.
Updated 04/29/2020
As the commenter rightly pointed out it would seem V8 was optimizing out the array includes calls. An updated version that assigns to a var and uses it produces more expected results. In that case Object address is fastest, followed by Set has and in a distant third is Array includes (on my system / browser).
All the same, I do stand by my original point, that if making micro-optimizations it is worth testing assumptions. Just make sure your tests are valid ;)
Original
Well. Despite the obvious expectation that Object address and Set has should outperform Array includes, benchmarks against Chrome indicate that implementation trumps expectation.
In the benches I ran against Chrome Array includes was far and away the best performer.
I also tested locally with Node and got more expected results. In that Object address wins, followed closely by Set has, then Array includes was marginally slower than both.
Bottom line is, if you're making micro-optimizations (not recommending that) it's worth benchmarking rather than assuming which might be best for your particular case. Ultimately it comes down to the implementation, as your question implies. So optimizing for the target platform is key.
Here's the results I got:
Node (12.6.0):
ops for Object address 7804199
ops for Array includes 5200197
ops for Set has 7178483
Chrome (75.0):
https://jsbench.me/myjyq4ixs1/1
This isn't necessarily a direct answer to the question but here is a related performance test I ran real quick in my chrome dev tools
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
var arr = [1,2,3];
var t = performance.now();
for (var i = 0; i < 100000; i++) {
var x = arr.includes(getRandomInt(3));
}
console.log(performance.now() - t);
var t = performance.now();
for (var i = 0; i < 100000; i++) {
var n = getRandomInt(3);
var x = n == 1 || n == 2 || n == 3;
}
console.log(performance.now() - t);
VM44:9 9.100000001490116
VM44:16 5.699999995529652
I find the array includes syntax nice to look at, so I wanted to know if the performance was likely to be an issue the way I use it, for checking if a variable is one of a set of enums for instance. It doesn't seem to be much of an impact for situations like this with a short list. Then I ran this.
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
var t = performance.now();
for (var i = 0; i < 100000; i++) {
var x = [1,2,3].includes(getRandomInt(3));
}
console.log(performance.now() - t);
var t = performance.now();
for (var i = 0; i < 100000; i++) {
var n = getRandomInt(3);
var x = n == 1 || n == 2 || n == 3;
}
console.log(performance.now() - t);
VM83:8 12.600000001490116
VM83:15 4.399999998509884
and so the way I actually use it and like lookin at it is quite worse with performance, despite still not being very significant unless run a few million times, so using it inside of an Array.filter that may run a lot as a react redux selector may not be a great idea like I was about to do when I decided to test this.

Restricted JavaScript Array Pop Polyfill not working

I'm creating a few specific functions for a compiler I'm working on, But certain restrictions within the compiler's nature will prevent me from using native JavaScript methods like Array.prototype.pop() to perform array pops...
So I decided to try and write some rudimentary pseudo-code to try and mimic the process, and then base my final function off the pseudo-code... But my tests seem to fail... based on the compiler's current behavior, it will only allow me to use array.length, array element assignments and that's about it... My code is below...
pop2 = function(arr) {
if(arr.length>0){
for(var w=undefined,x=[],y=0,z=arr.length;y<=z;y++){
y+1<z?(x[y]=arr[y]):(w=arr[y],arr=x);
}
}
return w;
}
Arr = [-1,0,1,2];
// Testing...
console.log(pop2(Arr)); // undefined... should be 2
console.log(Arr); // [-1,0,1,2]... should be [-1,0,1]
I'm trying to mimic the nature of the pop function but can't seem to put my finger on what's causing the function to still provide undefined and the original array... undefined should only return if an initial empty array is sent, just like you would expect with a [].pop() call...
Anyone have any clues as to how I can tailor this code to mimic the pop correctly?
And while I have heard that arr.splice(array.length-1,1)[0]; may work... the compiler is currently not capable of determining splice or similar methods... Is it possible to do it using a variation of my code?
Thanks in advance...
You're really over-thinking [].pop(). As defined in the specs, the process for [].pop() is:
Get the length of the array
If the length is 0
return undefined
If length is more than 0
Get the item at length - 1
Reduce array.length by 1
Return item.
(... plus a few things that the JavaScript engine needs to do behind the scenes like call ToObject on the array or ensure the length is an unsigned 32-bit integer.)
This can be done with a function as simple as the one below, there's not even a need for a loop.
function pop(array) {
var length = array.length,
item;
if (length > 0) {
item = array[length - 1];
array.length -= 1;
}
return item;
}
Edit
I'm assuming that the issue with the compiler is that Array.prototype.pop isn't understood at all. Re-reading your post, it looks like arrays have a pop method, but the compiler can't work out whether the variable is an array or not. In that case, an even simpler version of this function would be this:
function pop(array) {
return Array.prototype.pop.call(array);
}
Try that first as it'll be slightly faster and more robust, if it works. It's also the pattern for any other array method that you may need to use.
With this modification, it works:
http://jsfiddle.net/vxxfxvpL/1/
pop2 = function(arr) {
if(arr.length>0){
for(var w=undefined,x=[],y=0,z=arr.length;y<=z;y++){
if(y+1<z) {
(x[y]=arr[y]);
} else {
(w=arr[y],arr=x);
break;
}
}
}
return w;
}
Arr = [-1,0,1,2];
// Testing...
console.log(pop2(Arr)); // 2
The problem now is to remove the last element. You should construct the original array again without last element. You will have problems with this because you can't modify the original array. That's why this tasks are maded with prototype (Array.prototype.pop2 maybe can help you)

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.

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.

Javascript: Is the length method efficient?

i'm doing some javascript coding and I was wondering if the length method is "precomputed", or remembered by the JS engine.
So, the question is:
If I'm checking really often for an array length, and supposing that i'm not changing it (making it immutable through a closure), should I precompute the length method and store it in some variable?
Thanks!
As always, the answer is "it depends".
Let's test native arrays with a million-element array:
for (var i = 0; i < arr.length; i++);
var len=arr.length;
for (var i = 0; i < len; i++);
http://josh3736.net/images/arrlen.png
Chrome and Firefox optimize the property accessor to be as efficient as copying the length to a local variable. IE and Opera do not, and are 50%+ slower.
However, keep in mind that the test results' "ops/second" means number of complete iterations through an array of one million elements per second.
To put this in perspective, even in IE8 (the worst performer in this bunch)—which scored .44 and 3.9 on property access and local variable (respectively)—the per-iteration penalty was a scant 2 µs. Iterating over a thousand items, using array.length will only cost you an extra 2 ms. In other words: beware premature optimization.
The length of an actual array is not computed on the fly. It's stored as part of the array data structure so accessing it involves no more work than just fetching the value (there is no computation). As such, it will generally be as fast as retrieving any fixed property of an object. As you can see in this performance test, there is basically no difference between retrieving the length of an array and retrieving a property of an object:
http://jsperf.com/length-comparisons
An exception to this is the nodeList objects that the DOM returns from functions like getElementsByTagName() or getElementsByClassName(). In these, it is often much slower to access the length property. This is probably because these nodeList objects are not true javascript objects and there may be a bridge between Javascript and native code that must be crossed each time something is accessed from these objects. In this case, it would be a LOT faster (10-100x faster) to cache the length into a local variable rather than use it repeatedly in a loop off the nodeList. I've added that to the length-comparison and you can see how much slower it is.
In some browsers, it is meaningfully faster to put the length into a local variable and use it from there if you will be referring to it over and over again (like in a loop). Here's the performance graph from the above jsperf test:
All major interpreters provide efficient accessors for the lengths of native arrays, but not for array-like objects like NodeLists.
"Efficient looping in Javascript"
Test / Browser Firefox 2.0 Opera 9.1 Internet Explorer 6
Native For-Loop 155 (ms) 121 (ms) 160 (ms)
...
Improved Native While-Loop 120 (ms) 100 (ms) 110 (ms)
"Efficient JavaScript code" suggests
for( var i = 0; i < document.getElementsByTagName('tr').length; i++ ) {
document.getElementsByTagName('tr')[i].className = 'newclass';
document.getElementsByTagName('tr')[i].style.color = 'red';
...
}
var rows = document.getElementsByTagName('tr');
for( var i = 0; i < rows.length; i++ ) {
rows[i].className = 'newclass';
rows[i].style.color = 'red';
...
}
Neither of these are efficient. getElementsByTagName returns a dynamic object, not a static array. Every time the loop condition is checked, Opera has to reassess the object, and work out how many elements it references, in order to work out the length property. This takes a little more time than checking against a static number.
There's probably a modest speed boost attainable by caching the length in a local variable due to attribute lookup speed. This may or may not be negligible, depending on how the JS engine JITs the code.
See http://jsperf.com/for-loop-caching for a rudimentary JSperf testcase.
For any collection-type object whose length you will not be manipulating (e.g. any immutable collection), it's always a good idea to cache its length for better performance.
var elems = document.getElementsByName("tst");
var elemsLen = elems.length;
var i;
for(i = 0; i < elemsLen; ++i)
{
// work with elems... example:
// elems[i].selected = false;
}
elems = [10,20,30,40,50,60,70,80,90,100];
elemsLen = elems.length;
for(i = 0; i < elemsLen; ++i)
{
// work with elems... example:
// elems[i] = elems[i] / 10;
}

Categories

Resources