VOICE Home Page: http://www.os2voice.org |
September 2003
[Newsletter Index]
|
By Thomas Klein © September 2003 |
Last time we started to look at the first two types of program flow types: Batch (sequence) and branches (conditional expressions). Today, we'll conclude this discussion by looking at iterations - or 'loops' if you prefer.
Loops are basically used to repeat a certain processing until a specific condition
applies like reading each line (record) of a file from top to bottom until the end
of the file is reached. Loops are found in all programming languages. Basically,
they consist of three parts: Head, body and tail.
The body is made up of the command(s) to be repeated in each "lap" of
the loop, while head and tail are used to control the iterative process.
This is where things need to be explained in detail: Head and tail are not meant to be specific statements of a programming language but rather show their function in a structured scheme of how the loop works. Basically, we must differ between to types of loops (or how loops behave).
'Head-controlled' means, that the condition for repeating the loop (execution of the body part) is checked each time BEFORE the body is executed.
...are different in the way, that the condition for repeating the loop (executing the body part) is checked each time AFTER the body was executed.
This means, that if the condition for repeating the body part does NOT apply, a tail-controlled loop will run 1 time, while a head-controlled loop won't be executed at all. In Rexx, a head-controlled loop looks like this:
A tail-controlled loop (where the condition is checked after the body is executed) looks like this: DO WHILE <condition> <command-1> ... <command-n> END
The catch: Although both types of loop carry their respective condition in their "top", it doesn't mean that they are both head-controlled, as this simply ain't the case and the behave completely different. To get the difference, let's assume a loop which is repeated until the value of a variable named XYZ is greater than 4. Within each lap of the loop, the value of XYZ will be displayed and then incremented by 1. So far so good... now let's assume, that XYZ already holds an initial value of 7 before the loop is started. A head-controlled loop checks the condition before the body part is run, thus, using a head-controlled loop should result in nothing to happen: DO UNTIL <condition> <command-1> ... <command-n> END
Note that the condition is defined as "while xyz is not greater than 4") by using a backslash to "negate" the "greater" sign (">"). And in fact, if you run this piece of code, there'll be nothing displayed. XYZ = 7 DO WHILE XYZ \> 4 SAY XYZ XYZ = XYZ + 1 END
Now let's go for the tail-controlled type of the same loop (by using a condition made of UNTIL instead of WHILE)...
In this case, we used "until xyz is greater than 4" which quite equivalents "as long as xyz is not greater than 4". So actually there should nothing happen as well. But... bad luck! The body part will be run one time, although the condition doesn't apply - because this is checked AFTER the execution of the body part. XYZ = 7 DO UNTIL XYZ > 4 SAY XYZ XYZ = XYZ + 1 END
So far, this was about the basic components of "conditional loops".
Another "flavor" of loops is the one that will only run a specified amount of times. The REXX.INF file calls them "counter loops", but in fact they're nothing else but another kind of conditional loops as well with a "built-in" condition.
While conditional loops might run over and over until a certain condition applies (or no longer applies), those counter loops are repeated a fixed amount of times. The amount of repetition (the "laps") are specified in the loops head. Counter loops can be made up of a simple, direct amount of repetition like in...
Where <times> is a number. Here's a simple example: DO <times> <command-1> ... <command-n> END
The other type of counter loop (which is a lot more versatile and thus the one used in most cases) comes with a loop variable (or "counter variable" if you prefer) which is incremented by a given amount during each lap. The full syntax is: DO 3 say "hello" END
Example: DO <variable> = <start-value> TO <end-value> [ BY <increment> ] <command-1> ... <command-n> END
Now, as you might recall from previous syntax diagrams, the square brackets denote an optional part. Thus, the incremental value does not need to be specified. In this case, an increment of 1 is assumed. The same behavior as with the above example this can be achieved by coding: DO laps = 1 TO 10 BY 1 say "This is lap no.:" laps END
Note that if you want to do a "reverse" loop, you have to use a negative incremental value. If you don't, your loop will run forever, as the end-value for your counter loop won't ever be reached. Thus, if you want to have something like a "countdown loop", you go by this: DO laps = 1 TO 10 say "This is lap no.:" laps END
Of course, the start, end and incremental values entirely depend upon your needs. Note that the end value does not need to be hit exactly - if the counter value either matches the end value or is "beyond" the end value, the loop is terminated - example: DO laps = 10 TO 0 BY -1 say laps END
This will result in the counter starting with 7 and turn into 24, 41, 58, 75, 92 and finally 109. But 109 is larger than 94 (the end value) and thus the loop will terminate with the value of 92 being processed. DO myval = 7 TO 94 BY 17 say "current value is" myval END
...will output: DO myval = 7 TO 94 BY 17 say "current value is" myval END SAY "after loop:" myval
Thus, a counter loop actually behaves like a "head-controlled" conditional loop. The condition is checked prior to execution of the loop body. current value is 7 current value is 24 current value is 41 current value is 58 current value is 75 current value is 92 after loop: 109
This will output: DO myval = 99 to 1 BY -15 say "current value is" myval END say "after loop:" myval
Well, that's it for the basic principles of loops... happy "looping"! current value is 99 current value is 84 current value is 69 current value is 54 current value is 39 current value is 24 current value is 9 after loop: -6
Sometimes, one needs to react upon certain conditions while performing a loop. The most common cases are
name.1 = "Peter" name.2 = "Paul" name.3 = "Mary" name.4 = "Jack" name.5 = "John" name.6 = "Jim" name.7 = "Mom" name.8 = "Dad" phone.1 = "555-99887" phone.2 = "555-88776" phone.3 = "555-77665" phone.4 = "555-66554" phone.5 = "555-55443" phone.6 = "555-44332" phone.7 = "555-33221" phone.8 = "555-22110"
Now we want the program to start by saying "Please enter name", look up the name and display the phone number if a matching name is found. We assume the names to be unique (there can be only one "Peter" for example). Then, the program terminates.
Okay, we actually should use a loop here too which repeats the whole processing until "QUIT" was entered or something like this, but let's try to keep it simple at first:
After the above statements (the compound variable setup) was coded, here we go with the rest:
And that's it for the first attempt. SAY "Please enter name" PARSE PULL input DO entry = 1 to 8 IF name.entry = input then say name.entry ":" phone.entry END
But: The loop goes on to process the rest of the entries, which actually is not necessary because if a name was found, there can't be another one as we said the names are unique. So what we might need is a way of terminating the loop directly once a match was found. Imagine a phone book of 500 entries or more and the first entry already matches the input, butthe program keeps going through the rest of 499 entries... would you act the same way too?
And this is where REXX command LEAVE comes into action:
As we need more than one statement within the IF, we'll need to enclose them into DO...END (remember this from our previous article!). Now the loop will quit after displaying the match directly, skipping all remaining "laps". Hmm... I don't like it. If there's no match, nothing is displayed and the program just "silently" quits. No, no... we have to change it. Why not make use of what we know about loops so far? Take a look here: SAY "Please enter name" PARSE PULL input DO entry = 1 to 8 IF name.entry = input then DO say name.entry ":" phone.entry LEAVE END END
Yeah... that's a lot better... but what does it do? SAY "Please enter name" PARSE PULL input DO entry = 1 to 8 IF name.entry = input then LEAVE END if entry > 8 then say "No match found for <"input">" else say name.entry ":" phone.entry
First note that LEAVE will quit the loop with leaving the counter variable at its current value. Second, remember that if a loop was entirely processed, the counter variable is beyond the end value. Now all we did was make use of this behavior:
The program goes through one entry after another, incrementing the counter value. If a match was found, the loop is left by using LEAVE. If no match was found, the loop is terminated as well, because the counter variable has exceeded the end value. After the loop was left (by either of the two ways), we simply figure out HOW it was left: If the counter value now has a value that exceeds the end value of the loop, it means that all entries have been compared but no match was found. If not, there must have been a match and the counter variable holds the entry number of the match.
One more note: The programs is not THAT good, as it doesn't match names entered in upper or lower case. If you want it to be more useful, just enter all names in uppercase when setting up the compound variables. Next, omit the PARSE command for querying a user input. This will make REXX turn all input into entire upper case letters and you'll have perfect matches, regardless of whether "Peter", "PeTeR" or "peter" was entered. In addition, you might want to remove any leading or trailing blanks from the user input... so here's the complete program sample for you to play around with it:
/* REXX loop sample 1 */ name.1 = "PETER" name.2 = "PAUL" name.3 = "MARY" name.4 = "JACK" name.5 = "JOHN" name.6 = "JIM" name.7 = "MOM" name.8 = "DAD" phone.1 = "555-99887" phone.2 = "555-88776" phone.3 = "555-77665" phone.4 = "555-66554" phone.5 = "555-55443" phone.6 = "555-44332" phone.7 = "555-33221" phone.8 = "555-22110" SAY "Please enter name" PULL input input = strip(input) /* remove leading and trailing blanks from input */ DO entry = 1 to 8 IF name.entry = input then LEAVE END if entry > 8 then say "No match found for <"input">" else say name.entry ":" phone.entry /* end of sample code */
Skip the processing within a loops body to go on with next lap is achieved by using REXX command ITERATE. The benefits from using ITERATE increase the more complex the processing is which is "skipped". This means, that if your loop body is made up of a simple command, there is actually no need to use ITERATE. However, if a time-consuming process is part of each lap, ITERATE might speed up your loop tremendously.
Anyway - to show you an example, let's recall what people say about the 1970's: "If you remember the 70s, you weren't there".
Okay - why not do them a favor? Let's do a loop that omits the seventies from last centuries list of decades (starting with the 1940's)...:
Run this code to see what'll come out... ;) do decade = 40 to 90 by 10 if decade = 70 then ITERATE say "I still remember the" decade"'s" end
See how it works? All instructions within the body that follow ITERATE
will be skipped.
In the above example, execution of the body (consisting of the text output) will
be skipped if the counter variable equals 70.
Of course one could achieve the same without using ITERATE by coding:
In this case, the output text will only be displayed if the loop variable is NOT equal to 70. do decade = 40 to 90 by 10 if decade \= 70 then say "I still remember the" decade"'s" end
ITERATE thus is only an explicit command to show that a lap should be skipped if a certain condition applies - a self-explaining command to provide better readability if you want. But at least you now know that there is something called ITERATE. This will leave you more "flexible" in constructing program (loop) logic.
Well, that's it again for this month. According to my own road map, we'll take a look at the wealth of REXX's string manipulating functions. And if there is enough time left, we'll get into the subject of the REXXUTIL function library. That'll be both interesting and fun. Then, we'll get back to the actual DrDialog programming and see what were able to do now that we learned some basic REXX skills... and we'll play around with special tricks.
See you next month, same spot, same time! ;)
References:
|
[Feature Index]
editor@os2voice.org
[Previous Page] [Newsletter Index] [Next Page]
VOICE Home Page: http://www.os2voice.org