Page 1 of 1

The ultimate While loop

Posted: 01 Jul 2012 21:32
by Aacini
The purpose of this topic is to define a While loop that can be used in the easiest way. A step by step explanation of this problem follows, but if you just want to write a While loop, you may skip this description and pass to In Conclusion section below.

The idea is to write and execute a repetitive loop controlled by a condition, not by a limit or number of files that are directly managed with FOR command. However, in this description we will use a very simple loop as example:

Code: Select all

set index=1
:While
if %index% gtr 10 goto EndWHile
   echo Iteration number %index%
   set /A index+=1
   goto While
:EndWhile
The problem with this loop is that is slow. We use here a summary of the known features of these commands, a detailed description of this matter is discussed in this topic. We can assemble a much faster loop if we use an endless FOR /L command that is break with GOTO:

Code: Select all

set index=1
:While
for /L %%z in () do (
   if !index! gtr 10 goto EndWhile
   echo Iteration number !index!
   set /A index+=1
)
:EndWhile
The problem with this loop is that a FOR /L command can NOT be broken with GOTO command when it is excuted in the same cmd.exe context. A solution is to execute the FOR /L in another cmd.exe and break it with EXIT command:

Code: Select all

set index=1
:While
cmd /V:ON /Q /C WhileLoop
:EndWHile
WhileLoop.bat:

Code: Select all

for /L %%z in () do (
   if !index! gtr 10 exit
   echo Iteration number !index!
   set /A index+=1
)
Note that in this case the execution of cmd.exe must include the /V:ON switch in order to Enable Delayed Expansion in the WhileLoop.bat file.

We can include the While-body commands in the same Batch file and execute it with a special parameter (ie: "While") that indicate to execute the While-body instead of the original program, and an additional parameter to identify the While-body via its label. The While-body must be placed in a position that does not interfere with the original program; if we want to place the While-body in a visible position, it must be skipped when the original program run:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
rem While dispatcher
if "%1" equ "While" goto %2

rem Original program
rem . . . .
rem Skip the While-body
goto EndWhile

:WhileBody
for /L %%z in () do (
   if !index! gtr 10 exit
   echo Iteration number !index!
   set /A index+=1
)
:EndWhile

rem Execute the While
set index=1
cmd /Q /C %0 While WhileBody
Note that in this case the /V:ON switch is not necessary because cmd.exe execute this same Batch file and the While dispatcher is placed after the setlocal command that Enable Delayed Expansion.

We may improve this solution via Batch variables that aid to write clearer code. Also, we can reorder the IF inside the FOR /L in a way that allows the While condition be placed after the %While% auxiliary variable in a standard way:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
rem While dispatcher
if "%1" equ "While" goto %2
rem Define auxiliary While variables
set While=for /L %%? in () do if
set Do=(
set EndWhile=) else exit
set RunWhile=cmd /Q /C "%~F0" While

rem Original program
rem . . . .
rem Skip the While-body
goto :EndWhile

:WhileBody
%While% !index! leq 10 %do%
   echo Iteration number !index!
   set /A index+=1
%EndWhile%
:EndWhile

rem Execute the While
set index=1
%RunWhile% WhileBody
You must note that the While can NOT return any value via Batch variables to the caller code because it run in a separated cmd.exe. However, it can return a numeric result via ERRORLEVEL; the desired value may be placed after %EndWhile% macro and retrieved in the caller code after the %RunWhile%.

This method works executing the Batch file twice: the first time in normal way with %1 not equal "While" (when the While-body is skipped), and the second nested time with %1 equal "While" (when the While-body is executed). We may include this condition in the same %While%-%EndWhile% macro pair inserting an additional IF, but in this case the optional variable that define the ERRORLEVEL value returned by the While-body can not be included. We may choose a standard variable name for this purpose (ie: "WhileResult") and include it in %EndWhile% macro. This way, it is no longer necessary to skip the While-body and the While can be put in the place it will be executed in an entirely standard way:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
rem Define auxiliary While variables
set While=if "%1" equ "While" ( for /L %%? in () do if
set Do=(
set EndWhile=) else exit ^^!WhileResult^^! ) else cmd /Q /C "%~F0" While
rem While dispatcher
if "%1" equ "While" goto %2

rem Original program
rem . . . .

rem A standard While
set index=1
:WhileBody
%While% !index! leq 10 %do%
   echo Iteration number !index!
   set /A index+=1
%EndWhile% WhileBody
Note that in this case, While variable must be defined before execute the While dispatcher.

In Conclusion, to use a While in a Batch file:

1- Start your program with the first eight lines of the example program above (until the IF of While dispatcher).
2- Put a :label before the While and start it using %While% condition %Do% format. The condition is a standard IF command condition.
3- Close the While with %EndWhile% label using the same label before the %While%.
4- If you want the While returns a numeric result, assign it to WhileResult variable inside the While-body and retrieve it via %errorlevel% value after the %EndWhile%.

Remember that the values of variables modified inside the While must be retrieved via Delayed !variable! Expansion, but that this is not needed in SET /A command calculations, and that Delayed Expansion must be Enabled before execute the While; otherwise it is necessary to include /V:ON switch in the execution of cmd.exe in the EndWhile macro.

A small problem previous method have is that a While can not be nested inside another one. This problem may be solved differentiating %While% macro execution for each nested While via the %2 parameter, as shown in the program below. In this case, to use nested Whiles in a Batch file, modify previous steps this way:

1- Start your program with the first eight lines of the example program below.
2- Insert set WhileBody=label command followed by the :label and %While% condition %Do%.
3- Close the While with just %EndWhile%.

The example program below have two Whiles nested two levels deep; the nested While return a numeric result to the nesting one.

Code: Select all

@echo off
setlocal EnableDelayedExpansion
rem Define auxiliary While variables
set While=if %1-%2 equ While-^^!WhileBody^^! ( for /L %%? in () do if
set Do=(
set EndWhile=) else exit ^^!WhileResult^^! ) else cmd /Q /C "%~F0" While ^^!WhileBody^^!
rem While dispatcher
if "%1" equ "While" goto %2

set outer=10
set WhileBody=OuterLoop
:OuterLoop
%While% !outer! leq 100 %do%

   set /A inner=1, WhileResult=0
   set WhileBody=InnerLoop
   :InnerLoop
   %While% !inner! leq !outer! %do%

      set /A WhileResult+=inner, inner+=1

   %EndWhile%
   set iterative=!errorlevel!
   set /A calculated=(outer+1^)*outer/2
   echo Summation of 1..!outer!: Iterative method=!iterative!, Calculated result=!calculated!
   set /A outer+=10

%EndWhile%
Output:

Code: Select all

Summation of 1..10: Iterative method=55, Calculated result=55
Summation of 1..20: Iterative method=210, Calculated result=210
Summation of 1..30: Iterative method=465, Calculated result=465
Summation of 1..40: Iterative method=820, Calculated result=820
Summation of 1..50: Iterative method=1275, Calculated result=1275
Summation of 1..60: Iterative method=1830, Calculated result=1830
Summation of 1..70: Iterative method=2485, Calculated result=2485
Summation of 1..80: Iterative method=3240, Calculated result=3240
Summation of 1..90: Iterative method=4095, Calculated result=4095
Summation of 1..100: Iterative method=5050, Calculated result=5050


Antonio

Re: The ultimate While loop

Posted: 02 Jul 2012 01:33
by Ed Dyreen
'
Having a 'while' function that doesn't needs to jump back after every execution of the loop is very interesting.
The speed gain increases with longer batches, but even with the very shortest, I think there will still be gain.

The idea of using a cmd /c %while% &exit !errorLevel! is very clever.
We can get more info than just errorlevel, by placing the cmd /c command in a for /f loop and reading what it echos back.
viewtopic.php?p=11528#p11528 Thanks jeb :wink:

Thanks Aacini :)

Re: The ultimate While loop

Posted: 22 Oct 2022 04:39
by einstein1969
Congratulations on simplifying the code. It is extremely clean and is short and simple.

However, I found enormous problems with complex conditions in while loops. Example:

c++ code:

Code: Select all

(Offset<=MinOffset+255) && (Offset<=c)
or

Code: Select all

(File[c-Offset-NbMatch]==File[c-NbMatch])
            && (NbMatch<MaxNbMatch)
            && (c-Offset-NbMatch>=0)
and in the return of complex parameters such as arrays (implemented in cmd with multiple variables)

On the other hand, I found the version suggested by dbenham to be very simple and I developed a skeleton for nested while as well. I expected this version (with loop break with exit) to work faster or the same. Instead from a test made the version that calls the cmd.exe is twice as slow on my pc.

You find the example code here