Javascript for loop with object instead of counter? - javascript

Looking at this piece of code:
for (var i = 0, f; f = families[i]; i++) {
}
I haven't actually seen a loop like this before and I want to be sure I understand it correctly.
Am I correct in assuming that if families.length == 2 that the 2nd part of the for line would return false on f = families[2]?
I would have thought it would need to be something like f == families[2] in order to return false.

f = families[i] is an expression that returns the value of families[i]. (It also has the side-effect of assigning that value to f)
If families.length === 2 then families[2] === undefined thus the expression returns undefined which is falsey and breaks the loop.
For more hacking fun you can turn
for (var i = 0, f; f = families[i]; i++) {
// body
}
into
for (var i = 0, f; f = families[i++]; /* body */);
You may have to string replace ; with , and string replace i with i-1. You also just murdered readability.
It should also be pointed out that the for loop is silly for readability.
Object.keys(families).forEach(function(key) {
var family = families[key];
/* body */
});
Is significantly more readable.

This looks like kind of a silly way of doing
for(var i in families) {
if (families.hasOwnProperty(i)) {
// do whatever you want with families[i]
console.log(families[i]);
}
}

Related

Is it safe to use 'undefined' as sentinel in a Javascript while loop?

Is it safe to use this kind of loop in Javascript?
denseArray = [1,2,3,4,5, '...', 99999]
var x, i = 0
while (x = denseArray[i++]) {
document.write(x + '<br>')
console.log(x)
}
document.write('Used sentinel: ' + denseArray[i])
document.write('Size of array: ' + i)
It is shorter than a for-loop and maybe also more effective for big arrays, to use a built in sentinel. A sentinel flags the caller to the fact that something rather out-of-the-ordinary has happened.
The array has to be a dense array to work! That means there are no other undefined value except the value that come after the last element in the array. I nearly never use sparse arrays, only dense arrays so that's ok for me.
Another more important point to remember (thank to #Jack Bashford reminded) is that's not just undefined as a sentinel. If an array value is 0, false, or any other falsy value, the loop will stop. So, you must be sure that the data in the array does not have falsy values that is 0, "", '', ``, null, undefined and NaN.
Is there something as a "out of range" problem here, or can we consider arrays in Javascript as "infinite" as long memory is not full?
Does undefined mean browsers can set it to any value because it is undefined, or can we consider the conditional test always to work?
Arrays in Javascript is strange because "they are Objects" so better to ask.
I can't find the answer on Stackoverflow using these tags: [javascript] [sentinel] [while-loop] [arrays] . It gives zero result!
I have thought about this a while and used it enough to start to worry. But I want to use it because it is elegant, easy to see, short, maybe effective in big data. It is useful that i is the size of array.
UPDATES
#Barmar told: It's guaranteed by JS that an uninitialized array
element will return the value undefined.
MDN confirms: Using
an invalid index number returns undefined.
A note by #schu34: It is better to use denseArray.forEach((x)=>{...code}) optimized for it's use and known by devs. No need to encounter falsy values. It has good browser support.
Even if your code won't be viewed by others later on, it's a good idea to make it as readable and organized as possible. Value assignment in condition testing (except for the increment and decrement operators) is generally a bad idea.
Your check needs to be a bit more specific, too, as [0, ''] both evaluate to false.
denseArray = [1,2,3,4,5, '...', 99999]
for(let i = 0; i < denseArray.length; i++) {
let x = denseArray[i]
document.write(x + '<br>');
console.log(x);
if (/* check bad value */) break;
}
document.write('Used sentinel: ' + denseArray[i])
document.write('Size of array: ' + i)
From my experience it's usually not worth it to save a few lines if readability or even reliability is the cost.
Edit: here's the code I used to test the speed
const arr = [];
let i;
for (i = 0; i < 30000000; i++) arr.push(i.toString());
let x;
let start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
The for loop even has an extra if statement to check for bad values, and still is faster.
Searching for javascript assignment in while gave results:
Opinions vary from it looks like a common error where you try to compare values to If there is quirkiness in all of this, it's the for statement's wholesale divergence from the language's normal syntax. The for is syntactic sugar adding redundance. It has not outdated while together with if-goto.
The question in first place is if it is safe. MDN say: Using an invalid index number returns undefined in Array, so it is a safe to use. Test on assignments in condition is safe. Several assignments can be done in the same, but a declaration with var, let or const does not return as assign do, so the declaration has to be outside the condition. Have a comment abowe to explain to others or yourself in future that the array must remain dense without falsy values, because otherwise it can bug.
To allow false, 0 or "" (any falsy except undefined) then extend it to: while ((x = denseArray[i++]) !== undefined) ... but then it is not better than an ordinary array length comparision.
Is it useful? Yes:
while( var = GetNext() )
{
...do something with var
}
Which would otherwise have to be written
var = GetNext();
while( var )
{
...do something
var = GetNext();
}
In general it is best to use denseArray.forEach((x) => { ... }) that is well known by devs. No need to think about falsy values. It has good browser support. But it is slow!
I made a jsperf that showed forEach is 60% slower than while! The test also show the for is slightly faster than while, on my machine! See also #Albert answer with a test show that for is slightly faster than while.
While this use of while is safe it may not be bugfree. In time of coding you may know your data, but you don't know if someone copy-paste the code to use on other data.

How to remove spaces and special characters from string?

I have a function that returns true if a character is a form of punctuation and I'm trying to write another function that accepts a string and removes the spaces and punctuation marks while calling the first function. I got most of it I think. But now I'm stuck. Any help is appreciated.
var isPunct = function(ch) {
if (ch = ch.match(/[,.!?;:'-]/g))
return true
else
return false
}
//B
var compress = function(s) {
var result = "";
//loop to traverse s
for (var i = 0; i < s.length; i++) {
if (!(isPunct(ch));
//(isPunct(s.charAt(i) || s.charAt(i) == " "));
//do nothing
else
result = result + !compress(i)
}
return result
}
Some issues:
The inner condition should in fact be the opposite: you want to do nothing when it is a punctuation character, i.e. you don't want to add it to the result. Only in the other case you want to do that.
The call !compress(i) is wrong: first of all that function expects a string, not an index, and it returns a string, not a boolean (so to perform ! on it). It seems like you want to call your function recursively, and although that is an option, you are also iterating over the string. You should do one of the two: recursion or iteration.
You reference a variable ch in the compress function which you have not defined there.
So, if you want to write compress the iteration way, change your code as follows:
var compress = function(s) {
var result = "", ch; // define ch.
//loop to traverse s
for (var i = 0; i < s.length; i++) {
ch = s[i]; // initialise ch.
if (!isPunct(ch)) result = result + ch; // only add when not punctuation
}
return result;
}
If on the other hand you want to keep your recursive call to compress, then you should do away with your for loop:
var compress = function(s) {
var result = "", ch, rest;
if (s.length == 0) return '';
result = compress(s.substr(1)); // recursive call
ch = s[0];
if (!isPunct(ch)) result = ch + result;
return result;
}
The function isPunct also has a strange thing happening: it assigns a boolean value to ch in the if expression. This does not make your function malfunction, but that assignment serves no purpose: the match method already returns the boolean you need for your if condition.
It is also not really nice-looking to first evaluate a boolean expression in an if to then return that same value in the form of false and true. This you can do by just returning the evaluated expression itself:
var isPunct = function(ch) {
return ch.match(/[,.!?;:'-]/g);
}
On a final note, you don't really need the isPunct function if you only use it in compress. The whole logic can be performed in one function only, like this:
let compress = s => s.replace(/[,.!?;:'-]/g,'');
// Demo:
console.log(compress('a,b,c')); // abc
If you prefer to keep isPunct and don't want to repeat the regular expression elsewhere, then you can do the replace like this:
let isPunct = ch => ch.match(/[,.!?;:'-]/g);
let compress = s => Array.from(s).filter(ch => !isPunct(ch)).join('');
// Demo:
console.log(compress('a,b,c')); // abc
Note how the use of ES6 arrow functions and ES5 Array methods makes the code quite lean.

How to change this in object?

How to create method twice? I can't understand how to change this in body of function. Why it doesn't work?
function twice() {
var buf = [];
for ( var i = 0; i < this.length; i++ ) {
buf.push(this[i]);
}
for ( var i = 0; i < this.length; i++ ) {
buf.push(this[i]);
}
this = buf;
}
Array.prototype.twice = twice;
a = [1,2,3];
a.twice();
a; // [1,2,3,1,2,3]
I can't understand how to change this in body of function
If you mean the value of this, you can't. But you don't have to for what you're doing
You're very close, you just have a fair bit to remove:
function twice() {
var i, l;
for (l = this.length, i = 0; i < l; ++i) {
this.push(this[i]);
}
}
Remember, your array is an object. To change its contents, you just change its contents, you don't have to change the reference to it.
Note, though, that you can use this trick on any modern browser:
function twice() {
this.push.apply(this, this);
}
That works by using the Function#apply function, which calls the function you call it on (so, push in our case) using the first argument you give it as the object to operate on, and the second argument as the arguments to pass into that function (which it takes as an array). More on MDN and in the spec. It happens that push allows you to pass it an arbitrary number of arguments, and will push each one in order. So if you're trying to add the contents of the array to the array a second time, that one line will do it (on modern browsers, some older IE implementations don't like this use of push.apply).
You cannot assign a value to this. That's the rules. But you can modify the value of this. Try pushing some values into this.
function twice() {
var len = this.length;
for (var i = 0; i < len; i++) {
this.push(this[i]);
}
}
Array.prototype.twice = twice;
a = [1, 2, 3];
a.twice();
alert(a);
Here's a fiddle. http://jsfiddle.net/Qvarj/ As you can see, most of the logic is yours.

JavaScript Array Iteration returning more than values

This is so simple I am baffled. I have the following:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
for (t in stypes) {
if (stypes[t] != x) {
alert(stypes[t]);
}
}
Once the values have iterated it starts returning a dozen functions like
function (iterator, context) {
var index = 0;
iterator = iterator.bind(context);
try {
this._each(function (value) {iterator(value, index++);});
} catch (e) {
if (e != $break) {
throw e;
}
}
return this;
}
What the heck is going on?
Edit: In these scripts I am using http://script.aculo.us/prototype.js and http://script.aculo.us/scriptaculous.js I remember now reading about the way prototype extends arrays and I am betting this is part of it. How do I deal with it?
The for enumeration is going to go over every member of the object you passed it. In this case an array, which happens to have functions as members as well as the elements passed.
You could re-write your for loop to check if typeof stypes[t] == "function" or yada yada. But IMO you are better off just modifying your looping to only elements..
for(var i = 0, t; t = stypes[i]; ++i){
if (t != x) {
alert(t);
}
}
Or
for(var i = 0; i < stypes.length; ++i){
if (stypes[i] != x) {
alert(stypes[i]);
}
}
I wanted to migrate my last comment up to the answer to add the notice of the a caveat for the first type of loop.
from Simon Willison's "A re-introduction to JavaScript"..
for (var i = 0, item; item = a[i]; i++) {
// Do something with item
}
Here we are setting up two variables.
The assignment in the middle part of
the for loop is also tested for
truthfulness - if it succeeds, the
loop continues. Since i is incremented
each time, items from the array will
be assigned to item in sequential
order. The loop stops when a "falsy"
item is found (such as undefined).
Note that this trick should only be
used for arrays which you know do not
contain "falsy" values (arrays of
objects or DOM nodes for example). If
you are iterating over numeric data
that might include a 0 or string data
that might include the empty string
you should use the i, j idiom instead.
you want to do:
for (var i in object) {
if (!object.hasOwnProperty(i))
continue;
... do stuff ...
}
As for..in enumeration iterates over all properties (enumerable or otherwise) that exist on both the object and its prototype chain. The hasOwnProperty check restricts iteration to just those properties on the actual object you want to enumerate.
ES5 makes things a little better for library developers (and help avoid this stuff) but we won't see that ina shipping browser for quite a while :-(
[edit: replacing return with continue. lalalalala ;) ]
Since prototype has extended the array for your convenience you should take advantage of it. Your example could be rewritten as:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
stypes.without(x).each(alert);
It should be
for (t in stypes) {
if (t != x) {
alert(t);
}
}

javascript: speedy Array.contains(otherArray)?

I have an array of arrays. The inner array is 16 slots, each with a number, 0..15. A simple permutation.
I want to check if any of the arrays contained in the outer array, have the same values as
a test array (a permutation of 16 values).
I can do this easily by something like so:
var containsArray = function (outer, inner) {
var len = inner.length;
for (var i=0; i<outer.length; i++) {
var n = outer[i];
var equal = true;
for (var x=0; x<len; x++) {
if (n[x] != inner[x]) {
equal = false;
break;
}
}
if (equal) return true;
}
return false;
}
But is there a faster way?
Can I assign each permutation an integral value - actually a 64-bit integer?
Each value in a slot is 0..15, meaning it can be represented in 4 bits. There are 16 slots, which implies 64 total bits of information.
In C# it would be easy to compute and store a hash of the inner array (or permutation) using this approach, using the Int64 type. Does Javascript have 64-bit integer math that will make this fast?
That's just about as fast as it gets, comparing arrays in javascript (as in other languages) is quite painful. I assume you can't get any speed benefits from comparing the lengths before doing the inner loop, as your arrays are of fixed size?
Only "optimizations" I can think of is simplifying the syntax, but it won't give you any speed benefits. You are already doing all you can by returning as early as possible.
Your suggestion of using 64-bit integers sounds interesting, but as javascript doesn't have a Int64 type (to my knowledge), that would require something more complicated and might actually be slower in actual use than your current method.
how about comparing the string values of myInnerArray.join('##') == myCompareArray.join('##'); (of course the latter join should be done once and stored in a variable, not for every iteration like that).
I don't know what the actual performance differences would be, but the code would be more terse. If you're doing the comparisons a lot of times, you could have these values saved away someplace, and the comparisons would probably be quicker at least the second time round.
The obvious problem here is that the comparison is prone to false positives, consider
var array1 = ["a", "b"];
var array2 = ["a##b"];
But if you can rely on your data well enough you might be able to disregard from that? Otherwise, if you always compare the join result and the lengths, this would not be an issue.
Are you really looking for a particular array instance within the outer array? That is, if inner is a match, would it share the same reference as the matched nested array? If so, you can skip the inner comparison loop, and simply do this:
var containsArray = function (outer, inner) {
var len = inner.length;
for (var i=0; i<outer.length; i++) {
if (outer[i] === inner) return true;
}
return false;
}
If you can't do this, you can still make some headway by not referencing the .length field on every loop iteration -- it's an expensive reference, because the length is recalculated each time it's referenced.
var containsArray = function (outer, inner) {
var innerLen = inner.length, outerLen = outer.length;
for (var i=0; i<outerLen; i++) {
var n = outer[i];
var equal = true;
for (var x=0; x<innerLen; x++) {
if (n[x] != inner[x]) {
equal = false;
}
}
if (equal) return true;
}
return false;
}
Also, I've seen claims that loops of this form are faster, though I haven't seen cases where it makes a measurable difference:
var i = 0;
while (i++ < outerLen) {
//...
}
EDIT: No, don't remove the equal variable; that was a bad idea on my part.
the only idea that comes to me is to push the loop into the implementation and trade some memory for (speculated, you'd have to test the assumption) speed gain, which also relies on non-portable Array.prototype.{toSource,map}:
var to_str = function (a) {
a.sort();
return a.toSource();
}
var containsString = function (outer, inner) {
var len = outer.length;
for (var i=0; i<len; ++i) {
if (outer[i] == inner)
return true;
}
return false;
}
var found = containsString(
outer.map(to_str)
, to_str(inner)
);
var containsArray = function (outer, inner) {
var innerLen = inner.length,
innerLast = inner.length-1,
outerLen = outer.length;
outerLoop: for (var i=0; i<outerLen; i++) {
var n = outer[i];
for (var x = 0; x < innerLen; x++) {
if (n[x] != inner[x]) {
continue outerLoop;
}
if (x == innerLast) return true;
}
}
return false;
}
Knuth–Morris–Pratt algorithm
Rumtime: O(n), n = size of the haystack
http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm

Categories