I recently fell victim to what I thought was an infinite for-loop in javascript, due to the following code.
//Mongoose Schema, defined elsewhere
var foo = new Schema({
_id: Number,
a: [ Number ],
b: [ Number ] });
...
//loop code, running on node.js server
Foo.findOne({}, 'b').exec().then(function(result)
{
if(result)
{
for(var i = 0; i < result['a'].length; i++)
{
...
//oops, we used the wrong property!
}
console.log("This line is never reached!");
}
}
Because foo doesn't have property bar, foo['bar'] is undefined, and the comparator never returns true (and ending the loop).
But what surprises me most about this code, is that it doesn't crash. So I was curious - did comparing a number to undefined always equal true? Because the program loops infinitely, I know the comparison doesn't return false.
In an attempt to test, I swapped the less than to a greater than, but I still never reached the console output. So what is going on? Why does this not cause a runtime error, and what, actually is getting compared?
Actually, if the foo object doesn't have a bar property, it will throw a TypeError, because you are trying to access a property of an undefined variable. This should halt the script execution.
var a;
a.length; //TypeError: a is undefined
Also, I made some tests and comparing undefined to a number using the < operator always returns false.
0 < undefined; //false
1 < undefined; //false
Following this logic, the following shouldn't loop and that's what happens in my case:
var o = {};
for (var i = 0; i < o.length; i++) {
console.log(i);
}
Perhaps your assumptions are wrong?
Related
I know this might be basic and simple but since I am self-learning, so I wanted to know what was wrong and I thought this is the best place to learn from.
I am trying to write a recursive code that returns true or false. The condition to check is if the set of words can make the given target word.
The error I keet getting is :
if (targetString.indexOf(dictionary[i]) == 0) {
^
RangeError: Maximum call stack size exceeded
at String.indexOf (<anonymous>)
I am pretty sure that the problem with code is in a way I am returning because I always find it confusing.
my code is:
let targetString = "furniture";
let dictionary = ["fur", "ure", "nit"];
const tableData = {};
const canConstructRecursive = (targetString, dictionary) => {
if (targetString == "") {
return true
}
for (let i = 0; i < dictionary.length; i++) {
if (targetString.indexOf(dictionary[i]) == 0) {
shorterTargetString = targetString.slice(0, dictionary[i].length);
return canConstructRecursive(shorterTargetString, dictionary);
}
}
return false;
}
console.log(canConstructRecursive(targetString, dictionary));
I am learning recursion and from time to time I feel I don't understand the logic of return to next/previous recursive call.
I would really appreciate it if someone could help me with what I am doing wrong and change my way of thinking.
My way of thinking is that:
the base case is returned if it is reached at that stage otherwise loop go through all the option and the inner node or upper stack need to return value to lower stack so I am doing return canConstructRecursive() inside for. If even in all options which is all iteration of for loop, it is not returned, there is return false at the end.
Thank you in advance
The reason is that although your variable is named shorterTargetString, it is not guaranteed to be really shorter. If i is the index of the shortest word in dictionary, then there is no way your string will ever get shorter by recursing with it.
The mistake is that the slice should not start at 0, but after the part that was matched, so remove the first argument from the slice call.
This will solve the stack overflow error.
Secondly, if the recursive call returns false you should not give up, but keep trying with the next word. So only return out of the loop when you got true from recursion:
let targetString = "furniture";
let dictionary = ["fur", "ure", "nit"];
const tableData = {};
const canConstructRecursive = (targetString, dictionary) => {
if (targetString == "") {
return true
}
for (let i = 0; i < dictionary.length; i++) {
if (targetString.indexOf(dictionary[i]) == 0) {
shorterTargetString = targetString.slice(dictionary[i].length);
if (canConstructRecursive(shorterTargetString, dictionary)) {
return true;
};
}
}
return false;
}
console.log(canConstructRecursive(targetString, dictionary));
More on the second fix.
Your code will unconditionally return the value of the recursive call, even when it is false. This is not good: in case the recursive call returns false, the caller should continue with its for loop to try alternatives.
Let's for instance add a word to your example dictionary: you'll agree that adding a dictionary word should not change the outcome for the input "furniture". So here it is:
["furn", "fur", "ure", "nit"]
But surprise: your code now returns false for "furniture"! This is because "furn" mathes, but the recursive call with "iture" as first argument does not find further matches, so it returns false, and now the caller also returns false. This is wrong. It should give up on "furn", but not on the whole exercise. It should have continued and tried with "fur". This is why the exit out of the for loop should only happen upon success, not upon failure. Failure can only be confirmed when all dictionary words have been tried, so the for loop must continue for as long as there is no recursive success.
User trincot already explained what was wrong with your code. Here, I just want to point out that your structure, which is something like for (...) {if (...) { if (...) {return true} } } return false, might be better handled with Array.prototype.some and an && statement. Combining this with the fact that t .indexOf (s) == 0 might more clearly be expressed as t .startsWith (s), and sprinkling in a conditional statement instead of an if statement, we can arrive at what I think is a more elegant formulation:
const canConstruct = (t = '', ss = []) =>
t == ''
? true
: ss .some ((s) => t .startsWith (s) && canConstruct (t .slice (s .length), ss))
console .log (canConstruct ('furniture', ['fur', 'ure', 'nit'])) //=> true
console .log (canConstruct ('furniture', ['furn', 'fur', 'ure', 'nit'])) //=> true
console .log (canConstruct ('banana', ['b', 'ana'])) //=> false
console .log (canConstruct ('banana', ['ba', 'na'])) //=> true
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.
In the for loop: counter < (x.lenght) is spelled wrong, but the function returns zero. When corrected to x.length the function returns the correct number of Bs, 3. 1) Why is zero being returned? 2) Why does javascript not catch this error? 3) For the future, anything I can do to make sure these types of errors are caught?
function countBs(x){
var lCounter = 0;
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
Accessing x.lenght is returning undefined causing the for loop to terminate immediately. Therefore the initial value of lCounter is returned.
You can check for the existence of a property in an object by using the in keyword like so:
if ( 'lenght' in x ) {
...
x.lenght is returning undefined. Comparison operators perform automatic type juggling, so undefined is converted to a number to perform the comparison, and it converts to NaN. Any comparison with NaN returns false, so the loop ends.
Javascript doesn't catch this error because it uses loose typing, automatically converting types as needed in most cases.
There's no easy way to ensure that typos like this are caught. A good IDE might be able to detect it if you provide good type comments.
JavaScript does all kind of crazy coversions, instead of throwing an error: https://www.w3schools.com/js/js_type_conversion.asp
'undefined' in particular becomes NaN when necessary (very last line of the very last table), which results in 'false' when compared to a number (regardless of <, >, <=, >=, == or !=, they all fail, NaN does not even equal to itself).
If you want to catch or log an error to make sure your variable property is defined. Please see code below:
function countBs(x){
var lCounter = 0;
if(typeof x.lenght == 'undefined')
{
console.log('Undefined poperty lenght on variable x');
return 'Error catch';
}
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
To catch this particular error, set lCounter to -1 instead of 0.
That will ensure that the loop will run at least once if the for condition is correct.
You can return (or throw) an error if the loop isn't entered.
Otherwise, return lCounter + 1 to account for the initialization of -1.
function countBs(x) {
var lCounter = -1;
for (var counter = 0; counter < (x.lenght); counter++) {
if((x.charAt(counter)) == "B") {
lCounter++;
}
}
if(lCounter == -1) {
return 'Error';
} else {
return lCounter + 1;
}
}
You know that in Javascript you can access the length of an text/array with length property:
var obj = ["Robert", "Smith", "John", "Mary", "Susan"];
// obj.length returns 5;
I want to know how this is implemented. Does Javascript calculates the length property when it is called? Or it is just a static property which is changed whenever the array is changed. My question is asked due to the following confusion in best-practices with javascript:
for(var i = 0; i < obj.length; i++)
{
}
My Problem: If it is a static property, then accessing the length property in each iteration is nothing to be concerned, but if it is calculated on each iteration, then it cost some memory.
I have read the following definition given by ECMAScript but it doesn't give any clue on how it is implemented. I'm afraid it might give a whole instance of array with the length property calculated in run-time, that if turns out to be true, then the above for() is dangerous to memory and instead the following should be used:
var count = obj.length;
for(var i = 0; i < count; i++)
{
}
Array in JavaScript is not a real Array type but it's an real Object type.
[].length is not being recalculated every time, it is being operated by ++ or -- operators.
See below example which is behaving same like array.length property.
var ArrayLike = {
length: 0,
push: function(val){
this[this.length] = val;
this.length++;
},
pop: function(){
delete this[this.length-1];
this.length--;
},
display: function(){
for(var i = 0; i < this.length; i++){
console.log(this[i]);
}
}
}
// output
ArrayLike.length // length == 0
ArrayLike.push('value1') // length == 1
ArrayLike.push('value2') // length == 2
ArrayLike.push('value3') // length == 3
ArrayLike.pop() // length == 2
ArrayLike.length === 2 // true
var a = ["abc","def"];
a["pqr"] = "hello";
What is a.length?
2
Why?
a.length is updated only when the index of the array is a numeric value. When you write
var a = ["abc","def"];
It is internally stored as:
a["0"] = "abc"
a["1"] = "def"
Note that the indexes are really keys which are strings.
Few more examples:
1.)
var a = ["abc","def"];
a["1001"] = "hello";
What is a.length?
1002
2.) Okay, let's try again:
var a = ["abc","def"];
a[1001] = "hello";
What is a.length?
1002
Note here, internally array is stored as
a["0"] = "abc"
a["1"] = "def"
a["1001"] = "hello"
3.) Okay, last one:
var a = ["abc"];
a["0"] = "hello";
What is a[0]?
"hello"
What is a.length?
1
It's good to know what a.length actually means: Well now you know: a.length is one more than the last numerical key present in the array.
I want to know how this is implemented. Does Javascript calculates the length property when it is called? Or it is just a static property which is changed whenever the array is changed.
Actually, your question cannot be answered in general because all the ECMA specs say is this:
The length property of this Array object is a data property whose
value is always numerically greater than the name of every deletable
property whose name is an array index.
In other words, the specs define the invariant condition of the length property, but not it's implementation. This means that different JavaScript engines could, in principle, implement different behavior.
I wonder why seemingly equal loops lead to the different results.
First loop
for (var i = 0; i < 5; i++) {
console.log(i);
}
Results:
0
1
2
3
4
The result is fully understandable and expectable behaviour.
However, the following loop
var i=0;
while ( i<5) {
console.log(i);
i++;
}
leads to the different results, such that
0
1
2
3
4
4
As a beginner in programming I don't really understand why it is so, what the source of discrepancy in this case.
If you change to
var i=0;
while ( i<5) {
console.log("i is " + i);
i++;
}
then you will see in the console that it does output the correct values "i is 0", 1,2,3,4. The console also outputs the value of the last evaluated statement, which is why you see an additional 4.
If I run the for example in Google Chrome it prints an additional undefined because it outputs the value of the last evaluated statement, which is the console.log.
I think what you are seeing is an extra value printed by the interactive Javascript console that is the "result" of this code snippet.
The Javascript console prints the result of the for() and while() expression . If you run the first loop console :
for (var i = 0; i < 5; i++) {
console.log(i);
}
The results will be :
0
1
2
3
4
undefined
Here it prints the value of var i which is local to the scope of the for() loop , hence undefined outside it.
In the second case , just the var i is defined outside the loop body , hence it prints the latest value of i which is not undefined.
Since it seems the snippets are being evaluated, as through eval() or a REPL/Console, the extra 4 is the loop's return value.
And, yes, loops have return values.
Return (normal, V, empty).
Though, it's not something that's particularly useful to you in most cases, as attempting to retrieve it normally gives you a lovely SyntaxError. :)
var a = for (var i = 0; i < 5; i++) i;
console.log(a);
SyntaxError: Unexpected token for
However, eval() the loop, and you can see it.
var a = eval('for (var i = 0; i < 5; i++) i;');
console.log(a);
4
The value returned, V, comes from the evaluation of the loop's Statement. In the above case, that's simply i;, which is 4 the last time.
For your snippets, the for loop's result is undefined from console.log(i); and the while loop's result is 4 from i++;