Comp210: Principles of Computing and Programming
Fall 2004 -- Lecture #8   


Recursion

The dream in a dream in a dream in a dream in a......

As we talked about before, lists are self-referential data structures, that is, they a structures that contain one of their own type. For instance, we could define a list of numbers as such:

;; A list-of-numbers "lon" is either
;; - empty
;; - cons, which has a first, which is a number,
;; and a rest, which is a lon.
;; (cons num lon)

Since the definition of a lon includes a lon, we call this a "recursive definition".

Question: Isn't this a circular argument? How can we define anything in terms of itself? Is this definition really getting anywhere?

 

 

The answer is YES, it is a circular argument.....

 

Except that there are two parts to the definition (Gotcha!).

A recursive definition consists of at least 2 parts:

1) a base case that does not contain a reference to its own type.

2) an inductive case that does contain a reference to its own type.

The base case defines the terminating situation for the structure or process.

The inductive case defines how one can traverse the structure.

 

How does this recursive definition relate to the structure of a list? See the UML diagram of a list in the last lecture.

Composites are recursive!

 

What does the recursive definition of a list imply about the template?

Nothing out of the ordinary actually:

(define (f-lon ...a-lon...)

(cond

[(empty? a-lon)...]    ;; BASE CASE
[(cons? a-lon) ... (first a-lon)...(f-lon (rest a-lon))])) ;; INDUCTIVE CASE

ALL RECURSIVE ALGORITHMS CAN BE DECOMPOSED INTO A BASE CASE AND AN INDUCTIVE CASE.

Note that the f-lon in the inductive case does not necessarily have to be the same f-lon being defined!

 

Variant: The specific behaviors of the base and inductive cases.

Invariant: The code used to glue the base and inductive cases together into a working function = template code.

 

Guidelines for writing recursive algorithms:

  1. Focus on writing the base case and inductive cases separately! They have nothing to do with each other.

  2. Do not think about how the base and inductive cases are connected to the rest of the function -- that is the job of the invariant template code. This code does not even appear in some languages, e.g. declarative languages such as Haskell and ML as well as in well-written object-oriented programs.

  3. Remember that a cons list consists of first and rest only, so deal with just those two elements. DO NOT VIOLATE THE ENCAPSULATION OF REST!!

  4. The recursive call in the inductive case should be motivated by need, not by rote following of a recipe. This type of recursion,. "natural recursion", occurs because the function you need just happens to be the function you are defining.

Examples:

The cases are color-coded: base case & inductive case

Summing a list of numbers:

     ;; sum: lon -> num

     ;; Sums the numbers in a lon.

     (define (sum a-lon)

        (cond

            [(empty? a-lon) 0]

            [(cons? a-lon) (+ (first a-lon) (sum (rest a-lon)))]))



     "sum tests:"

     (= 10 (sum l1))

     (= 0 (sum empty))

     (= 2 (sum l2))

Advice: You should know how to hand-evaluate a recursive function...hint, hint, nudge, nudge!

Find a value in a list of numbers

     ;; find_val: lon num -> boolean

     ;; Returns true if x is in a-lon

     ;; otherwise returns false



     (define (find_val a-lon x)

        (cond

            [(empty? a-lon) false]

            [(cons? a-lon) (cond

                              [(= x (first a-lon)) true] ;;This is a "terminal case"

                              [else (find_val (rest a-lon) x)])]))



     " find_val tests:"

     (boolean=? false (find_val empty 42))

     (boolean=? true (find_val l1 3))

     (boolean=? false (find_val l2 3))

Class Exercise: Write a function to calculate the sum-of-squares of a lon.

Creating a new list: Copying a list

     ;; copy: lon -> lon

     ;; Copies a lon.

     (define (copy a-lon)

         (cond

             [(empty? a-lon) empty]

             [(cons? a-lon) (cons (first a-lon) (copy (rest a-lon)))]))



     "copy tests:"

     (equal? empty (copy empty))

     (equal? l1 (copy l1))

     (equal? l2 (copy l2))



     "Note, the following results in false:"

     (eq? l1 (copy l1))

equals? compares the value of every element, including the values contained in any composed structures.

eq? simply checks if they are identically the same entity, i.e. the same piece of memory allocation.

 

Class Exercise: Write a function to add a number to every element in a lon, returning the new lon.

The code for today, including the class exercises can be downloaded here.

Note: (list 1 2 3) is the same as (cons 1 (cons 2 (cons 3 empty)))

Warning!

(list 5 (list 3)) is not the same as (cons 5 (cons 3 empty)) !!!

(list 5 (list 3)) is the same as (cons 5 (cons (cons 3 empty) empty)) -- the second element is a list, not a number!

Don't use list when you should be using cons!!

 


Last Revised Tuesday, 24-Aug-2004 13:49:13 CDT

©2004 Stephen Wong and Dung Nguyen