1

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.
  • 3
    Well, that's not **just** `undefined` as a sentinel. If an array value is `0`, `false`, or any other falsy value, the loop will stop. – Jack Bashford Jun 08 '19 at 21:05
  • Good point! I have to edit :) –  Jun 08 '19 at 21:07
  • 2
    This will work, but it's unidiomatic so other programmers are going to find it confusing. I don't see how it's "more effective" than a `for` loop or using the `forEach()` method. – Barmar Jun 08 '19 at 21:10
  • 1
    It's guaranteed by JS that an uninitialized array element will return the value `undefined`. – Barmar Jun 08 '19 at 21:10
  • Maybe the conditional can be extended to only test on `undefined` as an alternative to allow other falsy values? –  Jun 08 '19 at 21:27
  • Any references on w3c or MDN to statements? Good to have in coding conventions manual to convince the safety of program. I think: as simpler code - as safer. Actually I don't lika for-loops and prefer while. –  Jun 08 '19 at 21:34
  • "Using an invalid index number returns undefined": https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array –  Jun 08 '19 at 21:52
  • 2
    Worth noting that most JS devs tend to prefer using higher-order functions to iterate over arrays. Somthing like `denseArray.forEach((x)=>{...code})` – schu34 Jun 08 '19 at 22:28
  • Yes, I think forEach is well optimized despite high level. Has good support! What I thought about effectiveness was to do the assignment in the conditional instead of closure. And there is no comparision expression. –  Jun 08 '19 at 23:01
  • "*It is shorter than a for-loop and maybe also more effective*" - it's neither, compared to `for (const x of denseArray)` – Bergi Jun 09 '19 at 11:54

2 Answers2

0

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.

Albert
  • 195
  • 9
  • You missed the assignment so I edited the post to put it in first row of closure. It could also be inside the parentheses. –  Jun 09 '19 at 09:23
  • Compare `for (let i = 0, x; i < denseArray.length; x = denseArray[i]; i++)` to `let i, x; while (x = denseArray[i++])`. They are pretty equal if data has no false values. Knowing more about big data may increase effectiveness by smart code adjustments. Maybe the while gets faster by skip `i < denseArray.length` test? –  Jun 09 '19 at 09:46
  • 1
    @PauliSudarshanTerho No, it doesn't get faster. When your `x = denseArray[i]` can access out-of-bounds indices because `i` is not limited to the length of the array, the js engine will need to do the bounds check implicitly when accessing the property. And that's very slow. JS engines are optimised for idiomatic code, not short code. – Bergi Jun 09 '19 at 11:56
  • JavaScript is pretty smart nowadays, but I tried both a for and a while loop ran on a 30M length string array. I ran the test in node on both versions 3 times after each other just to be sure. The result is (A: for, B: while) A 81 B 88 A 79 B 85 A 80 B 84. It seems like for is more readable **and** faster. – Albert Jun 09 '19 at 12:04
  • 1
    I added it to my answer – Albert Jun 09 '19 at 12:37
  • 1
    Me also added a jsperf test to my self-answer that confirme your test - The `for` is slightly faster than `while`. Nice test - I would use performance.now(). +1 for same conclusion as me. –  Jun 09 '19 at 14:11
  • Glad I could be of help. If you feel that I answered your question properly, I'd appreciate it if you marked my answer as accepted. – Albert Jun 09 '19 at 14:17
0

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.

  • I made a self-answer to put my findings instead of adding updates to the question. –  Jun 09 '19 at 14:27
  • Have now change to forEach in my code! I don't like the extra ')' ! It can be a struggle to put it right among several }}})}}} –  Jun 09 '19 at 14:36