Loops II - Safety and Efficiency

wonder

Expert
Licensed User
Longtime User
As you learned before, using loops will avoid code repetition and it can be a very efficient way to get things done.
However, if not handled properly, they do also hold the danger of freezing your entire app and/or causing an ANR warning.


Caution level:

1. The least "dangerous" kind of loop is the For...Next loop, because in principle it will "always end".
Consider the following code:
B4X:
'This code will most probably cause an ANR.
For i = 0 to 999999
    If File.Exists(File.DirAssets, i & ".txt") Then Log("True")
Next
Depending on the hardware, this example will indeed take some (maybe a lot of) time to complete, but nevertheless we can rest assured it will (eventually) complete.

2. The Do While/Until...Loop cycle should be regarded as a more "dangerous" kind of loop and has be used more responsibly.
B4X:
'This code will most probably cause an ANR.
Do Until n = 14
    Dim n = Rnd(0, 9999999999) As Long
Loop
This loop should end in a few seconds (minutes? hours?), but who knows, it might run forever. This kind of approach is not advisable at all.

Safety:

If you really have to use a "dangerous" loop in your app, a good practice would be implementing a timeout. Basically, exit the loop if it starts taking too long.
B4X:
Dim loopTimeout = 10000 '(ms) = 10 seconds
Dim loopStart = DateTime.Now As Long
Do (Until n = 14) Or (DateTime.Now - loopStart >= loopTimeout)
   Dim n = Rnd(0, 999999) as Int
Loop
This way you will always ensure that there will be an exit point in your loop. Optionally you may handle the timeout after you exit the loop. Here's how:
B4X:
Dim timeoutError = False As Boolean
Dim loopTimeout = 10000 '(ms) = 10 seconds
Dim loopStart = DateTime.Now As Long
Do Until n = 14 _
Or timeoutError
   Dim n = Rnd(0, 999999) as Int
   If (DateTime.Now - loopStart >= loopTimeout) Then timeoutError = True
Loop
If timeoutError Then
    Log("Sorry, bla bla bla...")
    ...
End If

Efficiency:

Not only you can apply the safety device described above, you can as well add some efficiency oriented instructions to your code. For this matter, we will use the Continue and Exit instructions.
In the example below, the Exit statement will terminate the For loop once the correct file is found.
B4X:
'Find and process ONLY the most recent yearly report.
For i = 2015 to 1977 Step -1
    If File.Exists(File.DirAssets, "year_report_" & i & ".txt" Then
        ...
        ...
        ...
        ...
        Exit
    End if
Next
...and here's a way to exit nested loops:
B4X:
For i = 0 to 100
    For j = 0 to 100
        If a(i, j) = "GOLD" Then Exit
        ...
        ...
        ...
    Next
    If a(i, j) = "GOLD" Then Exit
Next
Log("Gold was found in [A] at" & i & ", " & j)

The Continue statement will ignore all the loop code bellow and proceed to the next iteration:
B4X:
'Do not process ADMIN users
For i = 0 to (users.Lenght  - 1)
    If users(i) = "ADMIN" Then Continue 'at this point the current iteration is terminated and the loop proceeds to the next one.
    ...
    ...
    ...
    ...
    ...
    ...
    ...
Next
Of course, the same could be achieved with the traditional If/Then/Else statements but it's good to know we can also do it this way.

Long-story short: The Exit statement exits the whole loop. The Continue statement exits the current iteration.​
 
Last edited:

ilan

Expert
Licensed User
Longtime User
DoEvents is also a very important statement to mention

If you use a loop in a loop it is better to add DoEvents between the next loop.

It will take longer to finish this process but it will avoid crashes!


B4X:
for i = 0 to 100
   for i2 = 0 to 100
   'enter here you code

  next
  DoEvents 'to avoid crash
next

if you don't want to slow down your loop to much you can add DoEvents only if i mod 5 for example

B4X:
for i = 0 to 100
  for i2 = 0 to 100
  'enter your code here

  next
  if i mod 5 = 0 then DoEvents
next
 

HotShoe

Well-Known Member
Licensed User
Longtime User
Infinite loops are indeed a pain in the While and Until types of loops. I can't tell you how many times I have "created" one or more in some late night coding session. This is where the "think like a machine" starts to really pay off. Great addition @wonder.
 

Informatix

Expert
Licensed User
Longtime User
B4X:
For i = 0 to 100
    For j = 0 to 100
        If a(i, j) = "GOLD" Then Exit
        ...
        ...
        ...
    Next
    If a(i, j) = "GOLD" Then Exit
Next
Log("Gold was found in [A] at" & i & ", " & j)
I'd just like to mention that a more efficient and more generic version would be:
B4X:
Dim ExitLoops As Boolean
For i = 0 to 100
    For j = 0 to 100
        If a(i, j) = "GOLD" Then
              ExitLoops = True
              Exit
        End If
        ...
        ...
        ...
    Next
    If ExitLoops Then Exit
Next
Log("Gold was found in [A] at" & i & ", " & j)

And a word about performance:
A loop like this:
B4X:
For Each Item As String In MyList
    ...
Next
is faster than:
B4X:
Dim i As Int
Do While i < MyList.Size
   Dim Item As String = MyList.Get(i)
   ...
   i = i + 1
Loop
which is faster than:
B4X:
For i = 0 to MyList.Size - 1
    Dim Item As String = MyList.Get(i)
    ...
Next
 

ilan

Expert
Licensed User
Longtime User
B4X:
dim maxnr as int = 100
For i = 0 to maxnr
    For j = 0 to maxnr
        If a(i, j) = "GOLD" Then i = maxnr : j = maxnr
        ...
        ...
        ...
    Next
Next
Log("Gold was found in [A] at" & i & ", " & j)

also a possibility and also less lines :D
 

ilan

Expert
Licensed User
Longtime User
This syntax is "incorrect":
B4X:
If a(i, j) = "GOLD" Then i = maxnr : j = maxnr
The second part of this line will always be executed.

You need to write:
B4X:
If a(i, j) = "GOLD" Then
  i = maxnr
  j = maxnr
End If

yes you are right, in VB it would work.

Exit is missing in the condition; without it, the code after the condition will be run.

this is not correct because i set the value i and j to the maximum number of the loop so it will exit, you can see it in the screenshot i attached (log)

And I did not like to maintain such a code...

i agree with you, i have never used such a way to exit my loops, i used normally the way you suggested by checking a boolean in my loop.
i just wanted to take part of this discussion ;)

screen1.jpg
 

wonder

Expert
Licensed User
Longtime User
And a word about performance:
A loop like this:
B4X:
For Each Item As String In MyList
    ...
Next
is faster than:
B4X:
Dim i As Int
Do While i < MyList.Size
   Dim Item As String = MyList.Get(i)
   ...
   i = i + 1
Loop
which is faster than:
B4X:
For i = 0 to MyList.Size - 1
    Dim Item As String = MyList.Get(i)
    ...
Next
Thank you!! I didn't know about this! Mental note taken! I'll add this one to the "<> is faster than NOT()". :)
 

Informatix

Expert
Licensed User
Longtime User
this is not correct because i set the value i and j to the maximum number of the loop so it will exit
Your code works only if the condition is at the end of the inner loop and there's nothing else in the other loop but it's not what's implied by the wonder's code (note the lines with ...) and there's a faster way to find a value in an array otherwise.
 

ilan

Expert
Licensed User
Longtime User
Your code works only if the condition is at the end of the inner loop and there's nothing else in the other loop but it's not what's implied by the wonder's code (note the lines with ...) and there's a faster way to find a value in an array otherwise.

The end of the inner loop is 100 but you can see that i finish the loop with 78 (see logs)

So it will exit in any condition because i change the value to the maximum of the loop and like this there will not be any more loops.

Again its just a way not really the way i would choose or recommendet to someone else. Your suggestion is the correct one but my would also work :)

Try it yourself.
 

Informatix

Expert
Licensed User
Longtime User
The end of the inner loop is 100 but you can see that i finish the loop with 78 (see logs)

So it will exit in any condition because i change the value to the maximum of the loop and like this there will not be any more loops.

Again its just a way not really the way i would choose or recommendet to someone else. Your suggestion is the correct one but my would also work :)

Try it yourself.
You did not read my explanation.
Here's a case where your code should not be used:
B4X:
        For i = 0 To WhateverYouWant
        For j = 0 To WhateverYouWant
            If Myarray(i, j) = "Gold" Then
                 i = WhateverYouWant
                 j = WhateverYouWant
            End If
            If i > 50 Then
               Myarray(i, j) = "Silver"
            Else
               Myarray(i, j) = "Platinum"
            End If
            Log(i & " " & j & " = " & Myarray(i, j))
       Next
 Next
MyArray(100, 100) is set to Silver while it should not.
 

ilan

Expert
Licensed User
Longtime User
You did not read my explanation.
Here's a case where your code should not be used:
B4X:
        For i = 0 To WhateverYouWant
        For j = 0 To WhateverYouWant
            If Myarray(i, j) = "Gold" Then
                 i = WhateverYouWant
                 j = WhateverYouWant
            End If
            If i > 50 Then
               Myarray(i, j) = "Silver"
            Else
               Myarray(i, j) = "Platinum"
            End If
            Log(i & " " & j & " = " & Myarray(i, j))
       Next
Next
MyArray(100, 100) is set to Silver while it should not.

i don't want to argue with you or make you mad :D
but this is not fair

is like to tell you your code will not work like this:

B4X:
Dim ExitLoops As Boolean
For i = 0 to 100
    For j = 0 to 100
         ExitLoops = True
        If a(i, j) = "GOLD" Then
              Exit
        End If
        ...
        ...
        ...
    Next
    If ExitLoops Then Exit
Next
Log("Gold was found in [A] at" & i & ", " & j)

you should put the condition in the right place and it will work.

B4X:
For i = 0 To WhateverYouWant
        For j = 0 To WhateverYouWant
            If i > 50 Then
               Myarray(i, j) = "Silver"
            Else
               Myarray(i, j) = "Platinum"
            End If
            Log(i & " " & j & " = " & Myarray(i, j))
            If Myarray(i, j) = "Gold" Then
                 i = WhateverYouWant
                 j = WhateverYouWant
            End If
       Next
Next

but i agree with you it is not the right way but it will do the work...
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
i don't want to argue with you or make you mad :D
but this is not fair
You claim that your code is equivalent to the wonder's code. It is not. That's all. I never said it wouldn't be useful in some cases or that it does not work at all.

you should put the condition in the right place and it will work.

B4X:
For i = 0 To WhateverYouWant
        For j = 0 To WhateverYouWant
            If i > 50 Then
               Myarray(i, j) = "Silver"
            Else
               Myarray(i, j) = "Platinum"
            End If
            Log(i & " " & j & " = " & Myarray(i, j))
            If Myarray(i, j) = "Gold" Then
                 i = WhateverYouWant
                 j = WhateverYouWant
            End If
       Next
Next
NO! It's worse. You should really try the code.
 

Informatix

Expert
Licensed User
Longtime User
i never claimed that my code is equivalent to the wonder's code.
Just look at your original post. Your code in this post, which is explicitely written as an equivalent to the wonder's code (you even kept the "..."), cannot work as you wrote it. You introduced two errors. Erel explained the first; I explained the second. I don't know why we are losing time on that matter.
 

Daestrum

Expert
Licensed User
Longtime User
One other thing you need to consider.
Irrespective of how you write the code, the compiler will apply many optimizations to it, meaning the resultant executable code can be very different to how you write it.

Stick a '-server' before your jar (if its a server app obviously) and you get the compiler doing it's thing again on load. It will remove dead loops completely.
 

Informatix

Expert
Licensed User
Longtime User
One other thing you need to consider.
Irrespective of how you write the code, the compiler will apply many optimizations to it, meaning the resultant executable code can be very different to how you write it.
Yes, your code can be different after the Dalvik optimization, but no, the final result is still predictable.
If you write your loops like this:
B4X:
For i = 0 to MyList.Size - 1
   Dim Item AsString = MyList.Get(i)
   ...
Next
they won't be transformed like this:
B4X:
For Each Item As String In MyList
    ...
Next
The code which is slower in theory will be still slower in practice (not only for loops). So the way you write your code is very important.
 

Daestrum

Expert
Licensed User
Longtime User
Agreed but looking at the actual code produced (possibly down to how b4x translates into java) there is very minor differences.
forloop uses the standard for a = 0 to length-1
foreach uses for each c in ...
Yet the resultant code is so similar
B4X:
publicstaticString _foreach()
throwsException
{
String str = "";
anywheresoftware.b4a.objects.collections.List localList = _a;
inti = localList.getSize();
for(intj = 0; j < i; j++)
{
str = BA.ObjectToString(localList.Get(j));
Common.Log(str);
}
return"";
}


publicstaticString _forloop()
throwsException
{
inti = 0;
intj = _a.getSize() - 1;
for(i = 0; i <= j; i = 0 + i + 1) {
Common.Log(BA.ObjectToString(_a.Get(i)));
}
return"";
}
 
Top