Jonathan E. Sisk's
Pick/BASIC: A Programmer's Guide
Chapter 10 - Looping with the FOR-NEXT Statement
In example 8, iterative loop functions are covered, specifically, the FOR NEXT. Additionally, the FIELD, INDEX, COL1(), AND COL2( ) functions are discussed.
Enter Program Example 8, shown in Fig. 10-1.
ABOUT THE FOR-NEXT CONSTRUCT
In previous examples, several forms of "looping" constructs have been examined. To reiterate, a loop is a construct in which a series of instructions are repeated a certain number of times. In this example, the FOR-NEXT statement is introduced. This particular structure comes straight from standard Dartmouth BASIC, but a few twists have been added.
The basic premise of a FOR-NEXT construct is that the number of iterations to perform is defined in the FOR declaration statement, which has the general form:
FOR counter.variable = starting.expression TO ending.expression
The counter.variable is simply a variable which contains a numeric value. The first time that the FOR statement is evaluated and executed, the result of the starting.expression is assigned to the counter.variable. All of the statements up to the NEXT counter.variable statement are repeated until the value of the counter.variable is greater than or equal to the value of the ending.expression. The ending.expression also contains a numeric value which indicates the maximum number of times that the loop is performed.
A (possibly apocryphal) historical note: Virtually everyone uses the variable name I as the counter.variable. This is deeply rooted in history and comes to us from the old FORTRAN1 programming days. In FORTRAN, I was the first of the predeclared integer variable types. Old habits die hard. There is no rule that you must use "I" as your counter variable. Rather, I suggest you try to use descriptive variable names instead.
Fig. 10-4. Program Example 8
001 * EX.008 002 * LOOPING WITH FOR/NEXT, THE FIELD AND INDEX FUNCTIONS 003 * mm/dd/yy: date last modified 004 * JES: author's initials 005 * 006 PROMPT ":" 007 STRING = "" ; * SET WORK STRING TO NULL D008 * 009 * FORMAT SCREEN 010 * 011 PRINT @(-1): @(20,0): "EXAMPLE 8": @(58,0): TIMEDATE() 012 * 013 * GET NUMBER OF NAMES FOR UPPER END OF FOR ... NEXT 014 * 015 LOOP 016 PRINT @(3,3): "ENTER A NUMBER BETWEEN 5 AND 9": @(-4): 017 INPUT NUMBER 018 UNTIL (NUMBER >= 5 AND NUMBER <= 9) AND NUMBER # "QUIT" DO REPEAT 019 IF NUMBER = "QUIT" THEN STOP 020 * 021 FOR I = 1 TO NUMBER 022 LOOP 023 PRINT @(3,3+I) : "ENTER NAME NUMBER": I "L#5": 024 INPUT NAME 025 UNTIL NAME # "" DO REPEAT 026 IF NAME = "QUIT" THEN STOP 027 * 028 IF I # NUMBER THEN ; * IF NOT LAST TIME THEN APPEND "*" 029 STRING = STRING : NAME : "*" 030 END ELSE ; * IF LAST TIME JUST APPEND NAME 031 STRING = STRING: NAME 032 END 033 NEXT I 034 * 035 * DISPLAY THE STRING 036 * 037 PRINT @(3,13) : "HERE'S WHAT THE NAME STRING LOOKS LIKE : " 038 PRINT @(3,14): STRING 039 * 040 * GET THE NUMBER OF THE NAME TO RETRIEVE 041 * 042 LOOP 043 PRINT @(3,16) : "ENTER THE NUMBER OF THE NAME TO RETRIEVE" ' 044 INPUT NUMBER 045 UNTIL (NUMBER >= 1 AND NUMBER <= 9) AND NUMBER # "QUIT" DO REPEAT 046 IF NUMBER = "QUIT" THEN STOP 047 * 048 * GET NAME FROM STRING AND SHOW BEGINNING AND ENDING COLUMNS 049 * 050 PRINT @(3,17) :"NAME": NUMBER: "IS" :FIELD(STRING,"*",NUMBER) 051 PRINT @(3,18) : "IT BEGINS IN POSITION": COL1() + 1 052 PRINT 8(3,19): "AND ENDS IN POSITION": COL2() - 1 053 PRINT @(3,21) : "PRESS <cr> WHEN READY TO TEST INDEX FUNCTION" 054 INPUT PAUSE 055 * 056 * NOW LET'S CLEAR SCREEN FOR SECOND HALF OF PROGRAM 057 * 058 PRINT @(-1): 059 PRINT @(3,2) : "AGAIN, HERE IS WHAT THE NAME STRING LOOKS LIKE:" 060 PRINT @(3,3): STRING 061 * 062 * GET VOWEL 063 * 064 LOOP 065 PRINT @(3,5): "I'LL NEED A VOWEL (A,E,I,O, OR U) ": @(-4): 066 INPUT LETTER 067 UNTIL INDEX ("AEIOU",LETTER,1) OR LETTER = "QUIT" DO 068 PRINT @(3,6) : "SORRY.": LETTER: "IS NOT A VOWEL" 069 REPEAT 070 IF LETTER = "QUIT" THEN STOP 071 * 072 * COUNT THAT VOWEL AND SHOW HOW MANY WERE FOUND 073 * 074 NUMBER.VOWELS = COUNT(STRING,LETTER) 075 PRINT @(3,6) : "THERE ARE" : NUMBER.VOWELS : "OCURRENCES OF" : 076 PRINT LETTER 077 * 078 * NOW, SHOW EXACTLY WHERE THEY WERE FOUND 079 * 080 PRINT @(3,7) : "HERE ARE THE POSITIONS WHERE THEY WERE FOUND" · 081 FOR I = 1 TO NUMBER.VOWELS 082 POSITION = INDEX(STRING,LETTER,I) ; * FIND NEXT OCCURRENCE 083 PRINT @(2+POSITION,4) : "^" ;* PUT 'ARROWS' UNDERNEATH 084 PRINT @(3,8+I): "LETTER #": I: "'IS IN POSITION" :POSITION 085 NEXT I 086 * 087 * ALL DONE 088 * 089 PRINT 090 END
Statements between the FOR and the NEXT counter.variable statement are executed, and when program execution reaches the NEXT counter. variable, the counter.variable is automatically incremented by 1 (one). Program execution then transfers back to the start of the loop, where the FOR statement is found.
After going back to the start of the loop and incrementing the ending.expression, the current value of counter.variable is checked to see if it is greater than or equal to the value of ending.expression. If counter.variable is greater than or equal to the ending.expression, then the loop is considered done, and program execution transfers to the first executable statement after the NEXT counter.variable statement. Otherwise (if they are not equal) the loop executes again and the pattern continues.
The following example illustrates how the computer could be instructed to count from 1 to 10 and print the numbers along the way:
FOR I = 1 TO 10 PRINT I NEXT I
LOOPS WITHIN LOOPS
Before continuing with the explanation about the FOR-NEXT in Example 7, an important side trip needs to be taken to introduce the concept of loops within loops. Invariably, you will discover these in your programs and you will eventually find a need to include them yourself.
When a FOR-NEXT construct occurs within another FOR-NEXT construct, the "interior" loop acts almost like a single statement or function, meaning, that it will perform its designated number of iterations in each iteration of the exterior loop. For example, the following "nested" loop executes 100 times:
FOR I = 1 TO 10 FOR J = 1 TO 10 PRINT "I =" : I : "" : "J =" : J NEXT J NEXT I
Notice that the interior loop, which is referred to as the "J-loop" in this explanation, is entirely contained within the "I-loop." When this code executes, the variable 1, initially is set to 1. Then the "J-loop" begins, and J is set to 1. The next line of code prints the current value of I and J, and then J is incremented automatically by 1. Again, the current values of I (which has not changed), and J (which is now 2) are printed. The J-loop continues until J reaches 10. Just as soon as J-loop terminates, the I-loop increments I by 1, then checks to determine if I is 10. If I is 10 then both loops are done and the program continues at the next executable statement. Otherwise, ' 'J-loop' ' happens again. And again. And again.
I suggest that you indent source programs to assist in visually identifying "levels" of logic. The FOR-NEXT constructs are excellent examples of why this is so important. Without indenting the "levels" of logic, it becomes increasingly trickier to maintain programs.
FOR-NEXT IN CONTEXT
On line 21 of our example program (see Fig. 10-2), I is assigned the value of 1. Program execution then picks up on line 22, where a LOOP statement is started. Line 23 positions the cursor at position 3 on line 4 (which was calculated by adding the current value of I, which is still 1, to 3). The prompt "ENTER NAME NUMBER" is then followed by the current value of I (still the number 1), which is "masked" left-justified in a field of five blanks. The cursor is then held next to this prompt and the program awaits input on line 24. You have two choices, as usual. You may either enter a name for testing the example (use the seven dwarves' names if you can't think of any) or you may enter "QUIT," which means that you are ready to bail out.
The purpose of the exterior loop is to build a string of names, each of which is delimited by an asterisk. For example:
The number of names you are asked for depends upon the number you entered on line 16, when you were asked to enter a number between 5 and 9.
Consequently, on line 28 the program compares the current value of I to NUMBER (your number), to determine if this is the last time through the loop. If it is not the last time through, then, on line 29, the name you entered is concatenated to the end of STRING, followed by an asterisk. Otherwise, if it is the last time through, then on line 31 the last name you entered is concatenated to the end of STRING, without being followed by an asterisk.
On line 33, I is incremented by 1, and the program checks to see if I is equal to NUMBER. If they are equal, then the program "falls out" of the loop and continues execution on line 37 (the next executable statement). If they are not equal, then program execution transfers back to the top of the loop, in this case, back to line 21, where the process repeats.
021 FOR I = 1 TO NUMBER 022 LOOP 023 PRINT @(3,3+I) : "ENTER NAME NUMBER": I "L#5": 024 INPUT NAME 025 UNTIL NAME # "" DO REPEAT 026 IF NAME = "QUIT" THEN STOP 027 * 028 IF I # NUMBER THEN ; * IF NOT LAST TIME THEN APPEND "*" 029 STRING = STRING: NAME: "*" 030 END ELSE ; * IF LAST TIME JUST Applied NAME 031 STRING = STRING: NAME 032 END 033 NEXT I
Fig. 10-2. Main input loop of Program Example 8.
THE FIELD FUNCTION
There are many occasions (in programs) where you need to manipulate strings of characters that are delimited by known, yet unreserved (the reserved delimiters could actually be used in this function, but this rarely occurs. A special set of intrinsic functions are provided for dealing with the reserved delimiters, These are: INSERT, REPLACE, DELETE, EXTRACT, LOCATE, and the special "dynamic array" functions which use the < and > characters delimiters). Our example program demonstrates one such occasion:
050 PRINT @(3,17) :"NAME" :NUMBER :" IS " :FIELD(STRING,"*", NUMBER)
The reserved delimiters are the attribute mark, value mark, and subvalue mark . They are the special characters used to accommodate the Pick item structure. There are a handful of intrinsic functions used exclusively for dealing with attributes, values, and subvalues, which are discussed in Example 11.
The first portion of this program constructs a string of names, each of which is separated from the others by an asterisk. This string is stored in the variable STRING. The example provided was:
The string may now be manipulated with a special intrinsic function called FIELD. The FIELD function is used to locate and extract a string of characters from within another string of characters. Before the string may be extracted, however, two things must be known: the character that is being used as the "field" (or group) delimiter, and the relative number of the field to retrieve. The term "group" is used as a reference to the individual strings of characters, which happen to be names in this example. Note that the group, or field, delimiter may not be one of the reserved delimiters (attribute, value, or subvalue mark).
This example has seven groups, or "fields," each of which is separated from the others by an asterisk:
STRING = SLEEPY*DOPEY*GRUMPY*HAPPY*DOC*SNEEZY*BASHFUL Group number = 1 2 3 4 5 6 7
Given that these two pieces of information are known, any individual group may now be retrieved with the FIELD function which has the general form:
The string.variable is the variable which contains the groups, or fields, of strings to search through. The group.delimiter is the character (sometimes characters) that constitutes a group delimiter. The occurrence is a variable which evaluates to a number to indicate the number of the group to retrieve. As with most intrinsic functions, the FIELD statement, always appears in either an assignment statement (on the right side of an equals sign), or may be immediately printed or displayed with a PRINT or CRT statement.
Line 50 positions the cursor to position 3 on line 17, where it then displays the word NAME, followed by a space, followed by the number that you entered into the variable NUMBER on line 44. After another space, the word "IS" is displayed, followed by yet another forced space. Finally, the last thing done on line 50 is the FIELD function, which takes your number and searches through STRING until it finds that particular group. The name that corresponds with the group number you requested then displays on the screen.
This program required you to enter five to nine groups, and then required you to pick a number between 5 and 9 to extract that particular group. Things are not always this cut and dried. To further expand on this, suppose there were a variable called NAME that contained the value:
NAME = "LINCOLN, ABRAHAM"
To extract the last name seems pretty elementary. This could be done with the statement:
LAST.NAME = FIELD(NAME,",",1)
Executing this statement results in the string LINCOLN being assigned to the variable LAST.NAME. This statement literally reads: Search through the variable called NAME until you find the first delimiter (a comma) and extract all the characters up to that point in the string, not including the delimiter itself. The FIELD function never retrieves the group delimiter.
Getting the first name out of the string is a little trickier. Certainly, it requires using the FIELD function, but there will still be a "problem" remaining, i.e., how to get rid of the space before the first name. Consider the following statement:
FIRST.NAME = FIELD(NAME,",",2)
This statement literally reads: Search through the variable called NAME until you find the second comma delimiter, and extract all the characters from the first comma to the second. Executing this statement results in the string" ABRAHAM" (note the space) being assigned to the variable called FIRST.NAME. Since there is no second delimiter in the string, the FIELD function extracted all the remaining characters after the first comma, and placed the extracted string into FIRST.NAME.
Now, how might the extra space be removed? The answer is: Lots of ways. Remember the TRIM function? It is used to "trim" off extra leading and/or trailing spaces (as well as two or more embedded spaces). It could easily be used here, as in the following example:
FIRST.NAME = FIELD(NAME,",",2) FIRST.NAME = TRIM(FIRST.NAME)
That's one way. These two functions actually could have been combined into one more powerful, if a little more obscure, statement:
FIRST.NAME = TRIM(FIELD(NAME,",",2))
As we learned earlier, when introducing the concept of functions and expressions, functions may be nested within other functions. When this combination format is used, the program works outward from the innermost set of parentheses. As each successive function completes, the results that it produces is passed along to the next successive function.
Another method for extracting the first name requires the use of multiple FIELD functions, as in the following examples:
FIRST.NAME = FIELD(NAME,",",2) FIRST.NAME = FIELD(FIRST.NAME," ",2)
Again, these two functions could be combined into one statement:
FIRST.NAME = FIELD(FIELD(NAME,",",2)," ",2)
Fortunately, good sense steps in every once in a while and shouts in your ear, "Hey! Do you really want to have to support this later? So what if you trim 3 milliseconds off the processing time if it takes you 15 minutes to figure it out next year when you wander back through this code?"
There are at least two additional ways that the first name could have been extracted, but these previous methods are probably the most effective and efficient. One of the two additional ways available is to not store the space, which removes the extra processing time to remove it. Finally, the ACCESS "MCA" conversion could have been used in the form:
FIRST.NAME = FIELD(NAME,",",2) FIRST.NAME = OCONV(FIRST.NAME,"MCA")
Or they could have been combined into one statement:
FIRST.NAME = OCONV(FIELD(NAME,",",2),"MCA")
The MCA conversion code retrieves all of the alphabetic characters from a string. This means just the letters A-Z, in either the upper or lower cases.
THE COL1 AND COL2 FUNCTIONS
Each time a FIELD statement is executed, two special system functions, called COLl ( ) and COL2(), are updated. These functions retrieve the current values of special system variables.
051 PRINT @(3,18) :"IT BEGINS IN POSITION " :COL1()+1
COL1( ) contains the character position at which the beginning group delimiter was found in the last FIELD statement; COL2( ) contains the ending position where the group delimiter was found.
Observe the example string again, along with the columnar display of the character positions:
Character 1 2 3 4 Position 12345678901234567890123456789012345678901234 STRING SLEEPY*DOPEY*GRUMPY*HAPPY*DOC*SNEEZY*BASHFUL
Suppose that you had requested the third group from this string in the FIELD function. Afterward, COL1( ) contains 13, because the asterisk that precedes the "third" group was found in the 13th character position of the string. COL2( ) contains 20, since that is the position in which the terminating group delimiter was found.
Both the COL1( ) and COL2( ) may be used in calculations or may be assigned to another variable. An important note: Each time the FIELD function is executed, the values in COL1( ) and COL2( ) change.
For instance if you wanted to remove the third group, and its delimiter, the following statement could be used:
STRING = STRING[1,COL1()]: STRING[COL2()+I,LEN(STRING)]
This reads: Extract from the first position of the string all of the characters up to the current value of COL1(), which is 13 in the example. Consequently, this takes the string "SLEEPY*DOPEY*" and holds it in a temporary work area. The second portion of the statement tells the program to extract all of the characters after COL2(), using the LEN function, which determines how many characters there are in a string. This effectively extracts the string:
and joins it to the end of the former string in the temporary work area so that the string ends up as:
It might seem like a lot of work, but this is the way to manipulate strings of characters that are delimited by non-reserved delimiters. (Note that in most implementations of Pick, the "[" and "]" text extraction characters must be on the right side of an - symbol or in a PRINT statement.)
On line 51 of Example 8, the program moves the cursor to position 3 on line 18 of the screen and outputs the message, "IT BEGINS IN POSITION". The program then calculates the actual starting columnar position at which the string was found by taking the current value of COL1( ) and adding 1 to it. Remember that the COL1( ) function returns the position where the delimiter was found, not the position where the string began. Similarly, On line 52 the ending position of the string is calculated by taking the current value of COL2( ) and subtracting 1 from it.
ABOUT THE INDEX FUNCTION
The INDEX function is closely related to the FIELD function. It, too, is used to locate a string of characters within another string of characters, when the string being searched through is not delimited by reserved system delimiters.3 The main difference between the FIELD and the INDEX function, however, is that the FIELD function relies upon knowing the character that is being used as the delimiter and the "group" number to retrieve. Conversely, the INDEX function does not need to know which character is being used as the string delimiter. That's because the INDEX function is used to find a string of characters within another string of characters and to report the actual starting position of the desired string.
The INDEX function has the following general form:
The string.variable is the variable which contains a string of characters to search through. The search. string is the character (or characters) to be located within the string. variable. The occurrence is a variable that evaluates to a number to indicate which occurrence of the search.string of the group to retrieve. As with most intrinsic functions, the INDEX statement always appears in either an assignment statement (on the right side of an equals sign), or may be immediately printed or displayed with a PRINT or CRT statement. The INDEX function may also appear in a conditional expression (IF, CASE, LOOP). It is also capable of being used in calculated GOTO and GOSUB statements, referred to as ON-GOTO and ON-GOSUB.
Before explaining the mechanics of the INDEX functions provided in Example 8, study the following example which illustrates the general use of the function by finding DOPEY in the Seven Dwarves string:
001 STRING = "SLEEPY*DOPEY*GRUMPY*HAPPY*DOC*SNEEZY*BASHFUL" 002 SEARCH.STRING = "DOPEY" 003 POSITION.FOUND = INDEX(STRING,SEARCH.STRING,l)
In line 1 of this example, a string of characters is assigned to the variable, STRING. Line 2 assigns a variable called SEARCH.STRING, which is the string of characters to find within STRING. Line 3 is where the variable, POSITION.FOUND is assigned by using the INDEX function to find the first occurrence of DOPEY in the variable, STRING.
The effect of line 3 is that the value 8 is stored in the variable POSITION.FOUND, since the string DOPEY is found beginning in the eighth character position of the string.
INDEX in Program Example 8
Remember that conditional expressions return a value that represents either a true or false answer. "False" is always 0 (zero) or null. "True," however, may be any numeric non-zero. This example demonstrates the use of the INDEX function as a conditional expression in line 67:
067 UNTIL INDEX("AEIOU",LETTER,1) OR LETTER = "QUIT" DO
To illustrate this principle as a self-standing example, observe the following:
001 ALPHABET.STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 002 CHARACTER = "M" 003 POSITION.FOUND = INDEX(ALPHABET.STRING,CHARACTER,1)
In this example, after execution of line 3, the variable called POSITION. FOUND contains the value 13, since the letter M is found in the 13th character position of ALPHABET.STRING.
Suppose, however, that the object of the search is not located within the string, as in the following example where the INDEX function fails:
001 ALPHABET.STRING = "ABCDEFGHIJKLMNOPQRSTJVWXYZ" 002 CHARACTER = "@" 003 POSITION.FOUND = INDEX(ALPHABET.STRING,CHARACTER,1)
In this example, after execution of line 3, the variable called POSITION.FOUND contains the value 0 (zero), since the @ character is not found in any position of ALPHABET.STRING.
On line 66 of Example 8, you are asked to enter a vowel. The character (or characters) that you enter is stored in the variable called LETTER. Line 67 lists two conditional expressions, connected with an OR, which means either conditional expression may evaluate true to satisfy the UNTIL portion of the loop. There are, however, six possible responses that qualify as "true." As always, the "QUIT" bail-out mechanism is provided. The other five possible true responses are determined by the following INDEX function:
INDEX ("AEIOU" ,LETTER,1)
3. Again, the reserved system delimiters could be used in the INDEX function, but additional intrinsic functions have been added specifically to deal with the system delimiters.
This reads: If the variable called LETTER contains a letter that is found in the string "AEIOU," then the program returns the position number at which it is found. The resulting value is a numeric non-zero, or "true." If the letter is not found, the program returns a 0 (zero), or false, value. Regardless of the outcome, the result is returned to the UNTIL clause as the result of a conditional expression.
Note that the null string is always considered present in a string. This is illustrated in the following statements:
INDEX ("AEIOU", "" ,1)
This function retrieves the value 1, since the null is "found" at the beginning of the string.
Using Multiple INDEX Functions
The loop of statements between lines 64 and 69 effectively forced you to enter a vowel or to bail out. Line 74 takes the vowel you just entered into LETTER and counts the number of occurrences of that particular vowel within the string of names that you entered earlier into STRING.
Suppose, for example, that STRING contained the names:
STRING = "SLEEPY*DOPEY*GRUMPY*HAPPY*DOC*SNEEZY*BASHFUL"
Suppose further that you wanted to know how many occurrences of the letter E there were. This could be done with the statement:
NUMBER.VOWELS = COUNT (STRING, "E" )
After execution, NUMBERVOWELS contains the value 5, since there are five occurrences of the letter E in the string. Line 75 reports the number of occurrences of the specified vowel in your string.
On line 81, a FOR-NEXT construct is initiated, and the NUMBER.VOWELS variable is "set" as the upper boundary of the loop with the number calculated on line 74. This determines how many times the FOR- NEXT construct is to be executed. The loop counter variable, I, is assigned the initial value of 1.
On line 82, the INDEX function is called upon to determine the character position of the vowel, based upon the occurrence number determined by the current value of I. The first time through the loop, I is 1. The second time through, I is 2, and so on. If the vowel is found, then its corresponding character position is assigned to the variable called POSITION.
Line 83 calculates where the cursor is to be placed on the screen by adding the value of POSITION to the constant 2 (remember that the string display began in position 3), and then adding the value of POSITION. When the cursor position is calculated, an up-arrow (^) is placed at that spot on the screen.
Line 84 does more or less the same thing, but calculates the screen line number rather than the position number. Additionally, it prints the number of each occurrence of the vowel, along with the corresponding character positions.
Line 85 causes I to be incremented by 1 and then checks to see if I has become greater than or equal to NUMBER.VOWELS. If they are equal, then execution continues on line 89, where a PRINT statement issues a blank line on the screen and the program stops. If they are not equal, then program execution transfers back to the top of the loop, on line 81.
1) What does the FIELD function do?
2) Study the following assignment statement:
DESTINATIONS = "NEW YORK,ZURICH,PARIS,SINGAPORE,SYDNEY"
What statement determines how many destinations there are, and assigns the result to a variable called NUMBERDESTINATIONS? What statement, or statements, would extract the fifth destination and assign it to a variable called LAST.STOP?
3) What is the INDEX function used for?
4) Study the following statement:
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
What statement determines the character position of the letter S in the variable called ALPHABET?