COMP203: Lecture 14

for and recursion

When circumstances allow, I'll be typing up bits of my lecture notes and posting them online. These may or may not bear any resemblance to the actual lectures.

Review of for

Use for to write a Logo instruction that displays the numbers between 0 and 25 on the screen, counting by 5's.

Review of recursion

We've seen several examples of recursive procedures -- procedures that use themselves as helper procedures and so have a loop in their flowcharts. We learned that all recursive programs must have a "stop condition" to keep them from going into an infinite loop. Another key feature of recursive programming is what the author calls the "leap of faith". When you're writing a procedure that will be recursive, you have to assume that the procedure will work when used as a helper procedure.

Here's a sample procedure which takes a name as input and outputs the initials in the name:

to initials.recursive :name
 ifelse (emptyp :name)~
    [output []]~
    [output sentence (first (first :name)) (initials.recursive butfirst :name)]
What is the stop condition? Where is the recursive call? Must the stop condition be checked before the recursive call?

When making the recursive call, we assume that initials.recursive can successfully output "the rest of the list" of initials. This is the "leap of faith" the author mentions.

Accumulating values in recursion

Use the command trace initials.recursive and run the procedure on a few different inputs.

The procedure outputs [] when it reaches its stop condition, and longer and longer lists as it outputs the initials from different stages of "the rest of the list". This is typical of recursion. Our factorial procedure behaved similary, outputting 1 as its stop condition and larger and larger products as the program ran to completion.

to factorial :number
  if equalp :number 0 [output 1]
  output :number * factorial (:number - 1)
(What is the stop condition? Where is the recursive call?)

Can you write a recursive procedure which accepts a positive integer n as input and outputs the sum of the odd integers up to n? What about a maze game procedure that ends after the player makes a total of three lefts or one right, assuming that lefts and rights cancel? (The player wins as soon as he or she is facing in the correct direction.)

Accumulating values using for

Earlier we saw an example of a quiz procedure which used the following instructions to keep track of a student's score:
local "score
make "score 0
make "score sum :score 1
(Of course, the instruction to make "score sum :score 1 was inside the list of instructions to be followed if the user got the correct answer.)

This is typical of programs by authors who prefer using make to thinking about functional programming techniques. Here is our initials procedure again, using for instead of recursion.

to initials.for :name

  local "initiallist
  make "initiallist []  ; This is called initializing.  No pun intended.

  for [i 1 [count :name]] ~
      [make "initiallist sentence :initiallist first item :i :name]
  ; The list of instructions above adds the ith initial to the list.

  output :initiallist
Note that here we use item and make, where our functional programming (recursive) examples instead use butfirst and a "leap of faith" involving output. Also note that count :name is enclosed in brackets to force Logo to evaluate this instruction and get the target number.

There are two steps needed to accumulate values in a variable in this way:

  1. Initialize the variable, giving it a starting value which is often 0, 1 or the empty list. For example:
    local "varname
    make "varname 0
  2. Add on to the value stored in the variable, then store the new value in the old location. For example:
    make "varname :varname + :number
    Here's a procedure which calculates factorials using for:
    to factorial.for :num
      local "fac
      make "fac 1
      for [i 1 :num] [make "fac :fac * :i]
      output :fac
    Why did we make "fac 1 and not 0 in this procedure? Where does the initialization happen? Where do we add on to the value stored in the variable?

    Can you write a procedure which uses for to find the sum of the odd numbers from 1 to n? If so, can you write a procedure to find the sum of the first k odd numbers?

    Can you write a procedure which uses for and accepts a list as input then prints each item in the list? Which outputs the list in reverse?

    Recursion and Graphics

    Look at the example of the tree on page 193 of Chapter 10. Recursive procedures can be used to draw very pretty pictures. Here we'll talk about how to write a recursive procedure to draw one based on a square pattern.

    The hardest part of the problem is the leap of faith. Suppose we already had a procedure that could draw a small collection of nested squares. How would we write a procedure to add one square to that collection?

    What information do we need? We need the size of the new square that we're drawing, the size of the collection of squares to be drawn inside that square, and information on where to position the smaller collection of squares inside the large outer square.

    We can make the size of the square we're drawing an input to our recursive procedure:

    to squareview :size
    The size of the collection of squares inside the starter square will depend on the size of the starting square. After some thought, you may notice a 45-45-90 triangle at the corner of each square. If the two legs of that triangle have length :size/2, the Pythagorean theorem says that the hypotenuse must have length :size/sqrt(2).

    to square :size
     repeat 4 [forward :size right 90]
    to squareview :size
     square :size
     squareview :size / (sqrt 2)
    Our procedure isn't done yet. What are we missing?

    1. We need a stop condition. There is more than one right stop condition, but we must be sure that that stop condition is eventually reached. Our two chief options are: stop after we've drawn a certain number of squares, or stop when the squares get small enough. The second option seems simpler and more aesthetically pleasing to me, so I'll choose that one.

      The size of the squares is dictated by the variable size, so we just stop when the squares get small enough.

      to squareview :size
       square :size
       if lessp :size 2 [stop]
       squareview :size / (sqrt 2)
      (What happens if we mistakenly say if lessp 2 :size? Why don't we need an output command as we did with the factorial program?)

    2. We need to move the turtle into position to draw the smaller collection of squares. (If we do this step second, we can even run the procedure to see why we need this!)

      First, figure out where your turtle will be when it finishes drawing the outside square. Then figure out where it needs to move to to draw the next square.

      In this example, the turtle needs to move forward :size/2 distance, then turn to the right by 45° before starting the inside square.

      to squareview :size
       square :size
       if lessp :size 2 [stop]
       forward :size/2 right 45
       squareview :size / (sqrt 2)
    Remember that the command ht hides the turtle.