COMP203: Lecture 12

Midterm Exam moved to Monday, 3/24/14.

Assignment 6 due Wednesday, 3/26/14.

Flowcharts 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.

Chapter 6

Chapter 6 is about organizing and designing computer programs. If you don't have a clear idea of how your program is supposed to work, it's likely it won't. Reading Chapter 6 is one way to prepare for your midterm project.

In class we'll cover only flowcharts, which describe the "flow of control" in a computer program. Using a flowchart, you can diagram what choices the computer has at each decision point (if or ifelse statement). A flowchart can include as much or as little detail as you need.

Below is an example of a simple flowchart describing the process of waking up in the morning:

Notice that actions are described in rectangular boxes, decisions appear in diamond shaped boxes, and the start and end are in circles. Arrows indicate the "flow of control" in the program. (I don't know what the green bullet shape is for.) We'll display events in which the computer reads or displays something in parallelogram shaped boxes, as you can see from this flow chart describing a familiar quiz program.

Maze Game

Here is a flow chart describing a maze game. Save your work, then write a maze game that prints the responses shown in the parallelograms of the flow chart and makes decisions as described in the diamonds.

Chapter 7

Since early in the semester we've had in mind the question of how to separate a list into two lists -- what's the opposite of sentence? To do this, we need a way to repeat an operation for certain items in the list (e.g. adding them to a new list). We just saw an example of a maze game which asks a question repeatedly until a certain condition is met -- this could help us work with lists! Chapter 5 also has some tools that will be useful for this.

When a procedure is used as its own helper procedure in a program, we call that program recursive. Recursive programs are often the best way to apply functional programming techniques to a problem.

Recall our example of using Newton's Method to find the zeros of a function. Let's use it to estimate ln(4) by finding the value of x for which ex - 4 = 0.

to function :value
 output (exp :value) - 4
end

to derivative :value
 output exp :value
end

to improve_guess :guess
  output :guess - (function :guess) / (derivative :guess)
end
Once we've copied and pasted this little program into Logo, we can estimate ln(4) as follows:
? print improve_guess 2
1.54134113294645
? print improve_guess 1.5
1.39252064059372
? print improve_guess 1.4
1.38638785576643
? print improve_guess 1.386387
1.38629436541074
The values output by Logo have mostly stopped changing, so this answer is probably right. We can test it by plugging it in to procedure function and seeing if we get 0.
? print function 1.38629
-1.74444415237396e-005
? 
Yup, looks good!

That was annoying. The work of calculating the function and its derivative can't be avoided, but Logo should be able to repeat procedure improve_guess without our having to type it in a bunch of times.

Instead of typing improve_guess at the keyboard a large number of times we will use if and recursion to stop the procedure when our guess is improved to the desired accuracy. We'll do this by making changes to the improve_guess procedure to make it keep working until the guess is good enough.

How do we want to change improve_guess?

Instead of just outputting a guess, we want it to check to see if the guess is good enough. If it isn't, we want to improve it. If it is good enough, we want to output it. (You might think we'd want to print it, but that's not a good idea if we want to use this program for other tasks later.)

We come up with something like:

to improve_guess :guess
 if (good_enough :guess) output :guess
 output improve_guess :guess - (function :guess) / (derivative :guess)
end
This procedure requires a helper procedure (predicate) good_enough, which looks like:
to good_enough :guess
 output lessp abs function :guess .01
end
(Draw a plumbing diagram for output lessp abs function :guess .01. What does it do? Will this work?)

We can now test procedure improve_guess. If it doesn't work, we'll have to debug it. If it works, we can think about writing a nice, chatty procedure that asks for a guess and prints the solution. We could also think about ways of stopping improve_guess if Newton's method cannot find a solution, and about ways of allowing the user to type in a function as well as a guess.

More on Recursion

We've written a maze game like this one:
to maze.game
 print [You are lost in a cave.  Will you go left or right?]
 if equalp readword "right [print [You are saved!] stop]
 maze.game
end
This is an example of a recursive procedure. Recursive procedures have the following two properties: You may be familiar with inductive proofs or recursive definitions from your math classes. Proofs by induction use the following two ingredients: In proof by induction, the stop condition is like the base case and the recursive call is analogous to the inductive step. The inductive hypothesis in a proof by induction is like the leap of faith discussed in Chapter 8.

Recursive definitions are even more closely related to recursive procedures.Here's a recursive definition of n!, the factorial function:

Here's a recursive procedure that computes factorials:
to factorial :number
  if equalp :number 0 [output 1]
  output :number * factorial (:number - 1)
end
What's the stop condition? Where is the recursive call? Is this procedure an operation or command? What else do you notice about this procedure?

Variables and Recursion

In procedure factorial, the value of variable number changes as the procedure is run, and the output of factorial depends on the value input. To do something like split a list into two parts, we need to be comfortable using variables in recursive procedures.

The procedure below accepts a length as input and draws a spiral shape whose longest leg is that length.

to spiral :length
  if lessp :length 1 [stop]
  forward :length
  right 60
  spiral (:length - 10)
end
What is the stop condition? Where is the recursive call in spiral?

Play around with the spiral procedure -- what happens if you change the 60 or the 10? What happens if you change the order the instructions appear? Why?

Next, see if you can write a procedure that takes a number as input and displays a countdown from that number to 0. Use the spiral procedure as your guide. If you are successful, see if you can write a procedure to count up from 0 to the number input.

Can you write a Logo procedure that accepts a list as input and displays each member of the list alone on the screen? The Logo primitives first, butfirst and emptyp may be helpful here, or you might use count and item.

Next, see if you can write a maze game that "keeps score" -- each time the user makes a turn without escaping, add one to the score. When the player escapes, print their final score.

Common Mistakes in Recursion

Below is a recursive version of a procedure which counts down to 0. Save your work (save "W:/Private/recursionlesson.txt) before running it:

to countdown.recursive :number
 print :number
 countdown.recursive (:number-1)
end
Use the Logo menu to stop Logo. What went wrong?

Page 137 of Chapter 7 discusses the need for a stop rule that detects when a program has finished. In the case of countdown.recursive, you want to stop when the count reaches zero. Since your input might not be a positive integer, it's a good idea to play it safe and stop when the count is less than or equal to zero:

to countdown.recursive :number
 ifelse (greaterp :number 0)~
        [print :number
         countdown.recursive (:number-1)
        ]~
        [print "Blastoff!]
end
Note that you have to check to see if it's time to stop before the procedure runs itself as a helper procedure. Otherwise it goes into an infinite loop and doesn't stop to check if it's done. The procedure below provides an example of this common mistake -- save your work before running it.

to countdown.recursive2 :number
 print :number
 countdown.recursive2 (:number-1)
 if (lessp :number 1) [print "Blastoff! stop]
end

stop

Remember our old writers.quiz procedure:
to writers.quiz
  print [Who is the greatest writer ever?]
  if equalp readlist [Tolkien] [print [Yes, that's right!] stop]
  print [No, silly, it's Tolkien]
end
Here Logo's stop command is used to end the command writers.quiz before the instruction print [No, silly, it's Tolkien] is run. Try removing the stop command, and see what happens. stop is a command which forces the procedure it's in to quit. The textbook has an example of the use of stop to quit early in a procedure which uses the quadratic formula. In countdown.recursive2 we used it to stop a recursive procedure. In most other situations, it's best to try to avoid using stop. We used an if command to see if the countdown was finished. We could also have used ifelse, as in the example in the textbook.
to countdown.recursive3 :number
 ifelse (greaterp :number 1)~
        [print :number
         countdown.recursive3 (:number-1)
        ]~
        [print "Blastoff!]
end
If you do the extra credit game project, you may find that the last few lines of some procedure are run over and over when the program completes. This is usually caused by using if rather than ifelse at a decision point. Remember that when the the if statement completes, the computer will move on to run the next line of Logo code, even if you don't want it to! Experiment with procedure brokenmaze below to see what can happen. Can you fix brokenmaze so that it only prints At last! You escaped! once when you run it?

to brokenmaze
 print [You are lost in a maze.  Which way will you turn?]
 if equalp readword "left [brokenmaze]
 print [At last! You escaped!]
end
Summary: A recursive procedure is one that uses itself as a helper procedure. Every recursive procedure must have a stop condition (to prevent infinite loops) and a self-reference. Errors in the flow of control of a recursive procedure can cause infinite loops or other strange behaviors. See Chapter 7 for more examples of recursive procedures.