When iterating over a string or array (or anything else with a length property), I've always used a loop like this:
var foo = [...];
var i;
for(i=0; i<foo.length; i++) {
// do something
}
However, I just encountered someone who did this:
var foo = [...];
var fooLen = foo.length;
var i;
for(i=0; i<fooLen; i++) {
// do something
}
He said he thought the ".length" was recalculating the length, thus the loop would recalculate the length of the string/array over and over, so by saving its length to a variable it would be more optimized.
I always assumed length was just a value property because of the way it's used (it's not "asdf".length(), it's "asdf".length) but is this not the case?
There are some situations where putting the length into a local variable is faster than accessing the .length property and it varies by browser. There have been performance discussions about this here on SO and numerous jsperf tests. In a modern browser, the differences were not as much as I thought they would be, but they do exist in some cases (I can't seem to find those previous threads).
There are also different types of objects that may have different performance characteristics. For example, a javascript array may have different performance characteristics than the array-like object returned from some DOM functions like getElementsByClassName().
And, there are some situations where you may be adding items to the end of the array and don't want to be iterating through the items you add so you get the length before you start.
From MDC
for (var i = 0; i < a.length; i++) {
// Do something with a[i]
}
This is slightly inefficient as you are looking up the length property
once every loop. An improvement is this:
for (var i = 0, len = a.length; i < len; i++) {
// Do something with a[i]
}
Maybe not much of a difference with "regular" arrays, but for something like "node.children.length" I would err on the safe side and call it only once. CoffeeScript does that for you automatically.
Note that there is an actual difference in behaviour if the length can change during the loop.
It depends on if you are changing the foo's length.
var foo = [1,2,3];
while(foo.length){
foo.shift();
}
Obviously the code is keeping track of the foo's length, not simply remembering a value.
You can assign the length as a number in the loop.
for(i=0, L=foo.length;i<L; i++) {
// do something to foo[i]
}
Related
I'm learning Javascript and the for loop here looks exactly the same as in C. What I'm wondering is whether I should assign the length of a string to another variable in the loop. I usually do that in C when looping over a char array so the loop does not have to call strlen with each iteration:
for (int i = 0, n = strlen(word); i < n; i++)
{
// code block
}
What I see from following tutorials is that a for loop in Javascript is just written without assigning array length of a variable:
for (let i = 0; i < arr.length; i++) {
// code block
}
Is there any advantage to assigning the length of the array to a variable in Javascript?
No benefit, not as important as in C because arr.length is a static property i.e. it is not computed, unlike strlen which would compute the length each time it is called.
edit: see for yourself, its a controversial issue but it seems like accessing the property directly is fastest
What is the time complexity of a call to array.length in JavaScript? I think it would constant since it seems that property is set automatically on all arrays and you're just looking it up?
I think it would be constant since it seems that property is set automatically on all arrays and you're just looking it up?
Right. It's a property which is stored (not calculated) and automatically updated as necessary. The specification is explicit about that here and here amongst other places.
In theory, a JavaScript engine would be free to calculate length on access as though it were an accessor property as long as you couldn't tell (which would mean it couldn't literally be an accessor property, because you can detect that in code), but given that length is used repeatedly a lot (for (let n = 0; n < array.length; ++n) springs to mind), I think we can assume that all JavaScript engines in widespread use do what the spec says or at least something that's constant time access.
Just FWIW: Remember that JavaScript's standard arrays are, in theory, just objects with special behavior. And in theory, JavaScript objects are property bags. So looking up a property in a property bag could, in theory, depend on how many other properties are there, if the object is implemented as some kind of name->value hashmap (and they used to be, back in the bad old days). Modern engines optimize objects (Chrome's V8 famously creates dynamic classes on the fly and compiles them), but operations on those objects can still change property lookup performance. Adding a property can cause V8 to create a subclass, for instance. Deleting a property (actually using delete) can make V8 throw up its hands and fall back into "dictionary mode," which substantially degrades property access on the object.
In other words: It may vary, engine to engine, even object to object. But if you use arrays purely as arrays (not storing other non-array properties on them), odds are you'll get constant-time lookup.
It doesn't seem like a bottleneck but if you want to be sure use var len = arr.length and check that. It doesn't hurt and seems to be a tad faster on my machine albeit not a significant difference.
var arr = [];
for (var i = 0; i < 1000000; i++) {
arr[i] = Math.random();
}
var start = new Date();
for (var i = 0; i < arr.length; i++) {
arr[i] = Math.random();
}
var time1 = new Date() - start;
var start = new Date();
for (var i = 0, len = arr.length; i < len; i++) {
arr[i] = Math.random();
}
var time2 = new Date() - start;
document.getElementById("output").innerHTML = ".length: " + time1 + "<br/>\nvar len: " + time2;
<div id="output"></div>
Can this be optimized
var groups = [];
for (var a in aList) {
for (var bNumber in bList) {
groups.push({ a: a, b: b });
}
}
The code is actually fine, but I just realized that it looks like a cross product of two lists, so instead of looping aList.length*bList.length times, I wondered if there was some smart function to do this.
Julian pointed out a mistake in my original answer where I was storing elements as opposed to indices.
The existing code is erroneous; there is no variable b.
I will assume bNumber was meant to be b (or the other way around; it doesn't matter).
Since you know that arrays are indexed 0...n (I am assuming you declared the arrays in a way such that all indices in that range exist), you certainly shouldn't use a for...in loop (as mentioned in this comment);
rather, you should explicitly code in the bounds in a standard for loop (or some nearly identical variant):
var groups = [];
for (var a = 0; a < aList.length; a++){
for (var b = 0; b < bList.length; b++){
groups.push({a, b});
}
}
You shouldn't generally be using var either;
you generally want to restrict the scope of your variables to directly within the block in which you are working, and you want to be able to make some variables unassignable.
const groups = [];
for (let a = 0; a < aList.length; a++){
for (let b = 0; b < bList.length; b++){
groups.push({a, b});
}
}
With those changes, this has runtime of around 11% of the code in the answer, with the error fixed.
Original answer, assuming storing elements
First, since you know that you are dealing with lists, you should not use a for...in loop (as mentioned in this comment).
Next, this operation is generally called the Cartesian product or direct product, not the cross product.
The existing code is erroneous; there is no variable b.
I will assume bNumber was meant to be b (or the other way around; it doesn't matter).
The code can then be rewritten as
var groups = aList.flatMap(a => bList.map(b => ({a, b})))
(Although you shouldn't generally be using var either!
You generally want to restrict the scope of your variables to directly within the block in which you are working.)
You will almost always find that the code in the question (with the error fixed) performs better, though, because Array maps have to handle some additional logic costs, like orchestrating the callbacks and list concatenation.
Switching your for...in loops to for...of loops actually halves the runtime in my tests.
I am thinking to use the following function:
function delDups(arr){
var out=[],obj={};
for(var i=0,len=arr.length;i<len;i++){
obj[arr[i]]=0;
}
for(i in obj){
out.push(i);
}
return out;
}
The function is slightly modified, original at be found here
However, I am sure there are some values that will make it crash, and I want to know exactly what values will (so I can do soemthing about it)
Well, if arr isn't an array-like object (e.g. with a length and indexed properties) it will crash.
It may however also not do what you expect whenever the data in arr isn't an array of strings. In that case you'd get an array back with strings only, the original objects and their data type will be lost. But it will not crash...
Crashing is not the only inconvenient outcome from executing code. Also of (perhaps greater) interest are cases where the code runs but returns an incorrect result. Your analysis might be something like:
for(var i=0,len=arr.length;i<len;i++){
In the above, it is assumed that the value of arr.length is a numeric value greater than or equal to zero. If arr does not have a length property, or its value isn't a non-negative number, the for loop will not behave as expected (e.g. it may result in an error or an infinite loop).
obj[arr[i]]=0;
In this line, the result of evaluating arr[i] is used as a property name, so wherever that expression returns something that isn't suitable as a property name, you will get an error, e.g. if arr[i] is an ActiveX object, you can expect unexpected outcomes. If it's a native Object the value will be the result of calling its toString method, which might provide the same value for different objects, or an error, or "just work".
for(i in obj){
will iterate over all enumerable properties of obj, including those it inherits. If an enumerable property is added to Object.prototype, it will turn up in the loop so it's common to use a hasOwnProperty test to filter out inherited properties.
How much you test for errors depends on the environment you expect the code to be used in. If you have reasonable control and have documented the values that are expected to be passed to the function (e.g. array of primitive values) then it is reasonable to do minimal (or no) testing of input values. If someone passes in an ActiveX object instead of an array and it goes belly up, you respond with "RTFM".
On the other hand, if it is known that the code will be used in a library in uncontrolled and widely varying situations, testing that the input has a non-negative, numeric length property seems sensible, as does adding a hasOwnProperty test to the for..in loop.
How much time and effort you put into making your code robust is a function of where you expect it to run, but adding some sensible and obvious checks up front may well save some grief later. So I'd do something like:
function delDups(arr) {
var out=[],obj={};
var len = arr && arr.length;
if (len && len > 0) {
for(var i=0; i<len; i++) {
obj[arr[i]] = 0;
for (i in obj) {
if (obj.hasOwnProperty(i)) {
out.push(i);
}
}
}
return out;
}
Woohoo! I crashed it, where is my prize?
var arr3 = delDups(eval());
phew! That was a long title.
I'm reading WROX' book on Professional JavaScript for web developers and I came across this sample code, and I was just wondering if that was best practice:
function convertToArray(nodes) {
array = new Array();
for (var i=0, len=nodes.length; i < len; i++) {
array.push(nodes[i]);
}
return array;
}
The thing that's got me scratching my head is the "len=nodes.length". Am I wrong in thinking that the first sentence in a for-loop is only run once? Is there a reason you'd want to set a variable (len) to the length of the nodeList before running through it? Would you do that to a normal array as well?
Thanks
That is for performance reasons. A local variable is faster for several reasons:
The the length will need to be accessed all the time in the loop, once per every iteration;
A local variable lookup is faster than member lookup;
If nodes is an array, then .length is a magic property that may take a bit longer to retrieve than a member variable.
If nodes is an ActiveX object, then .length might result in a method call into the object, so that's the most expensive operation of all.
While we're discussing micro-optimizations, the following should be even faster:
function convertToArray(nodes) {
var i = nodes.length,
array = new Array(i); // potentially faster than `array = []`
// -- see comments
while(i--)
array[i] = nodes[i];
return array;
}
It needs one less local variable, uses a while and not a for loop and uses array assignment instead of the function call push().
Also, because we're counting down we pre-allocate the array's slots, the array's length doesn't have to be changed each iteration step, but only on the first one.