Lesson 9
Iteration

"Iteration" is a programming term that simply means repetition.  Iteration functions loop through the same set of programming instructions repeatedly.  AutoLISP has several iteration functions, but this lesson covers the three that are used most frequently -- repeat, while, and foreach.

The repeat function

The repeat function is the simplest of the three looping functions covered in this lesson.  The first argument is an integer, which indicates how many times the following expressions are to be repeated.  Then, after the first argument, there are one or more expressions to be repeated.  Here is a simple example of a repeat expression.  (Assume that the variable num has already been set to 100.)

    (repeat 10
       (setq num (1+  num))
       (princ num)
       (princ " ")
    )

The above expression produces the following display:

    101 102 103 104 105 106 107 108 109 110

Remember that the first argument must be an integer, not a real (or, of course, a variable holding an integer).

The repeat function is often found nested inside larger repeat functions or while functions.  The sample program below, called monthly, illustrates the repeat function nested to three levels.

When your program "knows" before entering the loop the number of iterations needed, the repeat function is the appropriate function to use.  When your program does not know the number of iterations needed, then the while function is more appropriate.

The while function

The while function is similar to the repeat function, but it also has several differences.  The first argument is a test rather than an integer.  This test is performed and the result determines whether or not the following expressions will be executed.  If the test passes (returns T), they are executed and then the test is performed again.  Every time the test passes, the expressions are executed and the test is repeated.  When the test finally fails, the expressions are not executed and the program continues with the next expression after the parenthesis that closes the while expression.  In programming terms, "control is passed" to the next expression after the while expression.

Below is a very simple "do-nothing" while loop.  The variable used in the test is potentially changed each time the loop is executed.  For that reason, the number of iterations is not known ahead of time.

   (setq ans "Yes")
   (while (= ans "Yes")
      (initget "Yes No")
      (setq ans (getkword "\nYes or No? (Y/N): "))
   )

The variable ans is set to "Yes" before the while loop begins.  This insures that the test in the while loop will pass the first time it is executed.

Whenever the user enters "Y," the loop repeats because the test passes.  (The getkword function returns "Yes" when the user enters "Y" because of the way it is initialized by the initget function.)  When the user enters "N," the test fails and the while loop ceases.

In the next example the same expression, (<= 1 var 100), is the test for both the while loop and the if expression.  The test returns T only when var is in the range of 1 through 100.  It is in that range the first time through the while loop because it is set to 50 before entering the while loop.  Each time the user enters an integer in the proper range, the test in the if statement passes, so the number is added to the total.  The test in the while loop also passes, so the expressions following the test are executed again, which results in the user being prompted for another integer.

   (setq tot 0 var 50)
   (while (<= 1 var 100)
      (setq var (getint "\nEnter an integer between 1 and 100: "))
      (if (<= 1 var 100)
         (setq tot (+ tot var))
         (princ "\nNumber out of range -- not added.")
      )
   )

The while-test-only form

Sometimes a while loop takes on a different appearance -- it has a test but no expressions following the test, as in the example below.  The test, which includes user input, is repeated every time the user enters a length that is greater than the upper limit (variable uplim, set earlier in the program).

   (while (not (<= (setq var (getreal "\nLength: ")) uplim)))

On the one hand, if the user enters an appropriate number, then the prompt is not repeated.  In this case the "less-than-or-equal" test returns T and the not function returns nil, so the while loop ceases.  On the other hand, if the user enters a length that is greater than uplim, then the "less-than-or-equal" test fails (returns nil) and the not function returns T, so the while function "executes" the expressions after the test (since there are none, of course, it does nothing), then it repeats the test which displays the prompt and asks again for the length.

The while function is often found nested inside larger repeat functions or while functions.

Practice session

Type out the three examples given above (directly at the command prompt), altering the numbers, tests, etc., until you are sure that you understand both repeat and while, and the differences between them.

The foreach function

The foreach function does a special type of iteration, based on the contents of a list.  The length of the list (the number of top level elements) determines the number of iterations.  Also, each element from the list is assigned to a variable so that it can be used in some way in the expressions that are iterated.  Here is a very simple example of a foreach expression.

    (setq nlist (list 1 2 3 4 5))
    (foreach v nlist (princ v) (princ "-"))

The above expression would result in the following display:

    1-2-3-4-5-

The first argument is a variable name.  This is the variable to which each element from the list, in turn, is assigned.

The second argument is the list.  Often the elements of the list are of the same type.  For example, they might all be numbers, or they might all be text strings, or they might all be lists (rather than atoms).  Other times the elements are of different types, but in that case, the expressions that follow the list must be able to handle the different types of elements.

Following the list are one or more expressions which are executed, once for each element in the list.

Here is another example of a foreach expression.  It is able to use the list containing both numbers and strings because the princ function is able to handle both of these element types.

    (setq num 14 rad 0.75)
    (setq plist (list "\nFound " num " circles, each with a radius of " rad "."))
    (foreach wrd plist (princ wrd))

The above expressions would produce the following display:

    Found 14 circles, each with a radius of 0.75.

The foreach function is quite powerful.  Since it works with a list, we will wait until we have covered lists in a later lesson before giving additional illustrations if its various uses.

Defining separate routines (modular programming)

As you begin to write longer programs, you will want to place certain portions of your program in separate routines (often called subroutines or modules).  It is especially advantageous to place a portion of a program in a separate module when that module will be called from two or more places in the main program.

The separate routine is defined (using defun) in the same file as the main program so that it will always be loaded when the main program is loaded.  It can be placed before or after the main program, but should not be placed within the main program.  (There are some more complex circumstances, particularly when building a set of interacting programs, in which it can be preferable to place some module definitions in separate files and other module definitions within the main program.  But for the present purposes, place them either before or after the main program, in the same file.)

There is an example of a separate routine at the beginning of the sample program below called monthly.

Sample program 1, ccircles

This program illustrates the while function.  It draws any number of concentric circles by asking the user for the common center and then for each different radius.

The test in the while statement contains a progn function.  You will recall, from the previous lesson on conditionals, that the progn function is sometimes used in an if expression in order to group several statements (lists) together into a single statement, since the syntax of the if expression expects a single statement for the then-portion and a single statement for the else portion.  There is a similar situation here.  The test in a while loop must be a single statement.  Since we need to initialize the getdist function by using initget, we also need to group the initget and the getdist functions into a single statement by using progn.  The progn function always returns the result of the last statement it executes, which in the case below will be the setq statement, which will return either a real number or nil.

------ marker ------ beginning of working program ------ try it out ------

;|  CCIRCLES, short for Concentric CIRCLES
    Copyright © 1988,1998 Ronald W. Leigh
    Input: common center, then any number of radii
    Output: Draws a circle after each radius is entered
Variables:
cen   Center
rad   Radius              |;

(defun c:ccircles (/ cen rad)
(initget 1)
(setq cen (getpoint "\nCenter: "))
(setvar "cmdecho" 0)
(while (progn (initget 6) (setq rad (getdist cen "\nRadius: ")))
  (command ".circle" cen rad))
(setvar "cmdecho" 1)
(princ)
)

-------- marker -------- end of working program -------- try it out --------

Sample program 2, curvtext

The curvtext program produces text that wraps around an arc or a full circle.  It illustrates the repeat function in section 40.

Curved text

The baseline can be a real or imaginary arc or circle.  When you create clockwise text, the letters are placed on the outside of the baseline.  When you create counterclockwise text, the letters are placed on the inside.  The text is placed as individual letters and the center of the base of each letter is equidistant from the letters before and after it.  So, even if your font is a variable pitch font, these letters end up mono spaced.  There are several variables which determine the spacing between letters.  You will need to experiment with different text heights, etc. in order to get the appearance you want.

Some letters pairs will need to be kerned because they will appear to have different spacing than others.  This is obvious with the letter "I" in the illustration above (which was left unkerned purposely) and other letters that are much narrower or much wider than the average width.  This problem also occurs with certain combinations of letters.  For example"LT" and "AV" will always appear too far apart.  You will want to manually kern (adjust the spacing of) some letter combinations by rotating letters and groups of letters around the center of the baseline arc.

This program includes an error routine, explained in a later lesson.  It also uses the UNDO command (sections 20, 50, and the error routine), to group the repeated TEXT commands into one "entity" making it possible to undo in a single step.
 

------ marker ------ beginning of working program ------ try it out ------

;|  CURVTEXT, short for CURVed TEXT
    Copyright © 1990,1998 Ronald W. Leigh
    Input: Baseline curve (center, start, end),
           text height and text
    Output: Text wrapped around arc or full circle
Variables:
a1    Angle from cen to sp, then to center of each letter
a2    Angle from center to end of arc
a3    Total included angle of arc
baa   Baseline angle adjustment
cen   Center of arc
cn    Character number
defhi Defined height of current text style
dir   Direction, clockwise or counterclockwise
ep    Ending point of arc
hei   Height of text
inc   Angular increment between letters
num   Number of characters in text
rad   Radius of arc
sp    Starting point of arc
temp  Temporary value
txt   Text to be placed on arc
vvv   Value for foreach function
vlist List of system variables                |;

;10====ERROR ROUTINE

(defun curv-error (es)
(setvar "blipmode" obm)
(setvar "osmode" oom)
(if (/= es "Function cancelled") (princ (strcat "\nERROR--" es)))
(setq *error* olderr)
(command ".undo" "e")
(setvar "cmdecho" 1)
(princ))

;20====MAIN PROGRAM, INITIAL SETUP

(defun c:curvtext (/ a1 a2 a3 baa cen cn defhi dir ep
                     hei inc num rad sp temp txt vvv vlist)
(setq olderr *error* *error* curv-error)
(setq obm (getvar "blipmode"))
(setq oom (getvar "osmode"))
(setq pi 3.1415926535897932385)
(setvar "cmdecho" 0)
(command ".undo" "c" "a" ".undo" "g")
(setvar "osmode" 0)
(graphscr)

;30====GET CENTER, TYPE, START, END, HEIGHT, TEXT

(initget 1) (setq cen (getpoint "\nCenter of baseline arc: "))

(initget "CW CCW")
(setq dir (getkword "\nClockwise or counterclockwise (CW/CCW) <CW>: "))
(if (null dir) (setq dir "CW"))

(initget 1) (setq sp (getpoint cen "\nStartpoint of baseline arc: "))

(initget 1 "C") (setq ep (getpoint cen
  "\nEndpoint of baseline arc (or \"C\" for full circle): "))
(if (= ep "C") (setq ep sp))
(setq rad (distance cen sp) a1 (angle cen sp) a2 (angle cen ep))
(if (= dir "CW")
  (if (>= a2 a1) (setq a2 (- a2 (* pi 2.0))))
  (if (<= a2 a1) (setq a2 (+ a2 (* pi 2.0)))))
(setq a3 (- a1 a2))

(setq defhi (cdr (assoc 40 (tblsearch "style" (getvar "textstyle")))))
(if (zerop defhi)
  (progn
    (setq hei (getdist sp
      (strcat "\nText height <" (rtos (getvar "textsize")) ">: ")))
    (if (null hei)
      (setq hei (getvar "textsize")) (setvar "textsize" hei)))
  (progn
    (setq hei defhi)
    (princ (strcat "\n(Fixed height = " (rtos hei) ")"))))

(setq txt (getstring 1 "\nText: ") num (strlen txt))

;40====PLACE TEXT IN DRAWING

(setq inc (/ a3 num) a1 (- a1 (/ inc 2.0)) cn 1)
(setvar "blipmode" 0) (setvar "cmdecho" 0)
(if (= dir "CW") (setq baa -90) (setq baa 90))
(repeat num
  (if (zerop defhi)
    (command "text" "c" (polar cen a1 rad) hei (+ (* a1 57.296) baa)
      (substr txt cn 1))
    (command "text" "c" (polar cen a1 rad) (+ (* a1 57.296) baa)
      (substr txt cn 1)))
  (setq a1 (- a1 inc) cn (1+ cn)))

;50====RESET ORIGINAL VALUES

(setvar "blipmode" obm)
(setvar "osmode" oom)
(setq *error* olderr)
(command ".undo" "e")
(setvar "cmdecho" 1)
(princ))

-------- marker -------- end of working program -------- try it out --------

Sample program 3, monthly

The repeat function has already been illustrated in two sample programs in previous lessons:

In this lesson we use a program called "monthly" to illustrates the repeat function in a slightly more advanced way.  It contains a simple repeat loop in the middle of the program, plus a more complex set of three nested repeat loops at the end of the program.  These repeat loops are clearly marked in the program.  It also contains a repeat loop in the "LEADING SPACES ROUTINE."

The program calculates monthly payments due on a loan.  It asks for the principal amount of the loan, the interest rate, and the number of years.  It then produces a table of monthly payments similar to the one shown below.  The table compares five different principal amounts, five different interest rates, and five lengths of loan.  (The middle principal amount, middle interest rate, and middle length of loan are the values entered by the user.  In other words, to produce the following table, the user would have entered 130000 for the principal amount, 8 for the interest rate, and 20 for the years.)

This program has little to do with typical AutoCAD usage.  Nevertheless, it is included here for two reasons.  First, it shows that AutoLISP is flexible enough to serve a variety of needs, not just design and drafting needs.  Second, it is a relatively simple example of nesting to three levels.

Examine the inner loop carefully.  It is executed 125 times, since it repeats five times inside a larger loop which repeats five times inside an even larger loop which repeats five times.  The inner loop uses the following formula in which P is the principal amount, r is the interest rate as a decimal fraction, and n is the number of monthly payments.

Monthly payment  =  P (r/12) (1 + r/12)n  /  ((1 + r/12)n - 1)

Since the program asks for years rather than number of monthly payments, it multiplies years by 12 inside the formula.  Also, since the program asks for the interest rate as a percentage (for example, 8 rather than .08), it compensates by dividing by 1200 where the formula calls for division by 12.

Notice the separate "LEADING SPACES ROUTINE" that is defined before the main program is defined.  This routine is needed in order to line up the columns of the table.  Some of the individual text strings (for example, the numbers representing the monthly payments) have more digits than others.  In order to get them to line up in columns, each must be lengthened to a given number of characters.  The routine that is defined as lesp (short for leading spaces) receives a string (variable str) along with an integer (variable tspac) indicating the total number of spaces (characters) desired.  It returns a string with the required number of spaces added at the beginning of the string.  This routine is called from two places in the main program:  the stand-alone loop near the middle of the program, and the inner loop near the end of the program.

------ marker ------ beginning of working program ------ try it out ------

;|  MONTHLY, short for MONTHLY mortgage payments
    Copyright © 1998 Ronald W. Leigh
    Input: principal, interest rate, years
    Output: five tables of monthly payments
     with neighboring values for comparison
Variables:
col    Column number (-2, -1, 0, 1, 2)
i      intermediate calculation in formula
mon    Monthly payment
r      Rate of interest for loops & formula
ra     Rate of interest (percent, as entered)
rai    Rate of interest looping increment
row    Row number (-2, -1, 0, 1, 2)
p      Principal for loops & formula
pr     Principal (amount of loan, as entered)
pri    Principal looping increment
str    String in leading spaces routine
table  Table number (-2, -1, 0, 1, 2)
tasep  Table separator (line of dashes)
tspac  Total spaces in leading spaces routine
y      Years for loops & formula
yr     Years (as entered)
yri    Years looping increment                |;
S
;====LEADING SPACES ROUTINE

(defun lesp (str tspac)
(repeat (- tspac (strlen str))
  (setq str (strcat " " str)))
(eval str)
)

;====MAIN PROGRAM

(defun c:monthly ()
(setq tasep
"-------------------------------------------------------------\n")
(initget 7)
(setq pr (getreal "\nPrincipal amount: "))
(initget 7)
(setq ra (getreal "\nInterest rate (as percent): "))
(setq yr 0)
(while (not (<= 11 yr 30))
  (setq yr (getint "\nYears (11 - 30) <20>: "))
  (if (null yr) (setq yr 20))
)
(princ tasep)
(princ "                       MONTHLY PAYMENTS\n")
(princ tasep)
(princ "Princ:")
(setq pri 5000.0 col -3)
(repeat 5                                   ;-----------------------
  (setq col (1+ col))                       ;STAND-ALONE LOOP       |
  (setq p (+ pr (* col pri)))               ;makes header line of 5 |
  (princ (lesp                              ;principal amounts      |
    (strcat "$" (rtos p 2 0)) 11))          ;                       |
)                                           ;-----------------------
(terpri)
(setq rai 0.25 table -3)
(repeat 5                                   ;------------------------
  (setq table (1+ table))                   ;OUTER LOOP              |
  (setq r (+ ra (* table rai)))             ;makes 5 tables          |
  (princ tasep)                             ;                        |
  (princ "                       ")         ;                        |
  (princ (rtos r 2 3))                      ;                        |
  (princ "% interest")                      ;                        |
  (terpri)                                  ;                        |
  (setq yri 5 row -3)                       ;                        |
  (repeat 5                                 ;---------------------   |
    (setq row (1+ row))                     ;MIDDLE LOOP          |  |
    (setq y (+ yr (* row yri)))             ;makes 5 lines        |  |
    (princ (lesp (itoa y) 2))               ;in each table        |  |
    (princ " yrs")                          ;                     |  |
    (setq pri 5000 col -3)                  ;                     |  |
    (repeat 5                               ;------------------   |  |
      (setq col (1+ col))                   ;INNER LOOP        |  |  |
      (setq p (+ pr (* col pri)))           ;makes one line of |  |  |
      (setq i                               ;5 monthly payments|  |  |
        (expt (1+ (/ r 1200.0)) (* y 12)))  ;                  |  |  |
      (setq mon                             ;                  |  |  |
        (* p (/ (* (/ r 1200.0) i) (1- i))));                  |  |  |
      (princ (lesp (rtos mon 2 2) 11))      ;                  |  |  |
    )                                       ;------------------   |  |
    (terpri)                                ;                     |  |
  )                                         ;---------------------   |
)                                           ;------------------------
(princ tasep)
(princ)
)

-------- marker -------- end of working program -------- try it out --------
 

HOME


Copyright © 1988, 1998 Ronald W. Leigh