Recursion examples, Foundations of Computing, Fall 2000

Here are some examples of recursive functions in Scheme.

You can copy and paste these examples into your Scheme interpreter. In DrScheme, paste them into the Definitions Window.

It is informative to trace these functions. In DrScheme, put the expression (require-library "trace.ss") at the top of the Definitions window.

It is informative to trace both the recursive function being defined and the combining function that forms the results. For example, in rev turn on tracing by typing this expression in the Interaction window: (trace rev append). To turn off tracing, use (untrace rev append).

Sequences
Binary Trees
Other Trees

Numbers

Last updated November 21, 2000 (to clarify the distinction between binary trees and other trees). Use your browser's Reload or Refresh button to get the latest version.


Sequences

You can treat a list as a sequence of elements, processing the list from the beginning to the end. Use this form when the list contains no sublists, or when you don't need to deal with the internal structure of the sublists.

Here is the basic pattern:

(define (f lst ...)                            ;; One of the arguments is a list
  (if (... base case ...)                      ;; Often (null? lst)
      ... value to return for base case ...    ;; Often '()
      (g ... (f (cdr lst) ...) ...)))          ;; Recursive call applies f to (cdr lst) 
                                               ;; Combining function g forms the result
                                               ;; Often g is cons or append
                                               ;; Often another arg. to g is (car lst)

One of the arguments to the function is a list. The base case is usually the empty list. Each recursive call invokes the function on the cdr of the list, which guarantees that the list gets smaller on each recursive call until the base case is reached. The recursive calls work their way down the list until the end. The results of the recursive call are usually combined with something else (often the car of the list) by another function (often cons or append) to form the results.

This technique is called single recursion because there is a single recursive call in the body of the function.

This example returns a list, the input list reversed.

;; Like Scheme reverse
(define (rev lst)
  (if (null? lst)
      '()
      (append (rev (cdr lst)) (list (car lst)))))

This example returns a number, the length of the list.

;; Like Scheme length
(define (len lst)
  (if (null? lst)
      0
      (+ 1 (len (cdr lst)))))

This example searches the list and returns the list from the point where the search was csuccessful. If the search is unsuccessful, it returns #f.

;; Like Scheme member
(define (memb x lst)
  (if (null? lst)
      #f
      (if (equal? x (car lst))
          lst
          (memb x (cdr lst)))))

This example shows how to define a higher order function using recursion. The first argument f is itself a function.

;; Like Scheme map
(define (mapper f lst)
  (if (null? lst)
      '()
      (cons (f (car lst)) (mapper f (cdr lst)))))

This example shows how to define Scheme append using only car, cdr, and cons.

;; Like Scheme append, ex 17.6 from Simply Scheme
(define (app lst1 lst2)
  (if (null? lst1)
      lst2
      (cons (car lst1) (app (cdr lst1) lst2))))

This example is much like rev above but reverses a word (an atom) not a list.

;; backwards - reverse a word
;; Called reverse in Simply Scheme p 192 and 192.
;; Requires last and bl from simply.scm or simply-teachpack.scm
(define (backwards wd)
  (if (empty? wd)
      ""
      (word (last wd)
            (backwards (bl wd)))))

Binary Trees

Tree recursion treats a list as a branching data structure, exploring each branch. Use this form when the list contains sublists, and you must deal with the internal structure of the sublists.

When the sublist in the car of the input should be treated the same as the sublists in the cdr, you can treat the list as a binary tree where each branch divides in two. The car of each list (or sublist) is the left branch and the cdr of each list is the right branch.

Here is the basic pattern:

(define (f struc ...)                          ;; One of the args is a structured list
  (cond (... base case 1 ...) ... value1 ...)  ;; Often (null? struc), return '()
        (... base case 2 ...) ... value2 ...)  ;; Often (word? struc), return struc
        (else (g ... (f (car lst))             ;; Recursive call applies f to (car lst)
                     (f (cdr lst))))))         ;; Recursive call applies f to (cdr lst)
                                               ;; Combining function g forms the result
                                               ;; Often g is cons or append

This looks almost the same as the pattern for single recursion, except there are two recursive calls in the body of the function, one for the car and the other for the cdr of the structure (instead of just for the cdr, as in single recursion). The recursive call on the car guarantees that the function will dig into nested sublists as deeply as necessary. Often there are two base cases as well, one for the empty list and another for an atom (tested by word? in Simply Scheme, roughly equivalent to (not (list? ...)) using standard scheme functions).

Notice the symmetry between the car and the cdr in the two recursive calls.

This function flattens a structured list:

;; Ex 17.12 from Simply Scheme
(define (flatten struc)
  (cond ((null? struc) '())
        ((word? struc) (list struc))
        (else (append (flatten (car struc)) 
                      (flatten (cdr struc))))))

This function counts all the words (that is, atoms) in a structured list:

;; Ex 17.13 from Simply Scheme
(define (deep-count lst)
  (cond ((null? lst) 0)
        ((word? lst) 1)
        (else (+ (deep-count (car lst))
                 (deep-count (cdr lst))))))

Other Trees

Binary trees have just two branches from each node. Other trees may have several branches from each node. These trees can be represented as lists where the car of the list (and each sublist) represents the data at the node and the cdr of the list (and each sublist) is the list of branches attached to that node (called the forest). The leaves of the tree are represented by lists with only one element, the data at that leaf (the cdr of the leaf nodes are empty lists, indicating there are no more branches.

An example of such a tree appears in Simply Scheme chapter 18, on page 315 (in the second edition). Another example appears in the solution to problem 2 in our November 9 quiz.

In trees like this, the car of each list must be treated differently than the cdr because it plays a different role --- the car and cdr are not symmetrical.

Simply Scheme handles these trees using two procedures that exhibit mutual recursion --- each procedure calls the other. The argument to the first procedure is the list itself (that is, the whole tree). The first procedure calls the second, whose argument is the cdr of the list (that is, the list of branches or forest). The second procedure in turn calls the first on the car of the this list and calls itself on the cdr.


Numbers

Here is the basic pattern:

(define (f n ...)                              ;; One of the arguments is a number
  (if (... base case ...)                      ;; Often (= n 0) or (= n 1)
      ... value to return for base case ...    ;; Often 1 or 0
      (g ... (f (- n 1) ...) ...)))            ;; Recursive call applies f to (- n 1) 
                                               ;; Combining function g forms the result
                                               ;; Often g is + or *

One of the arguments to the function is a number. The base case is usually zero or one. Each recursive call invokes the function on the number minus one, which guarantees that the number gets smaller on each recursive call until the base case is reached. The results of the recursive call are usually combined with something else by another function (often + or *) to form the results. When the combining function is +, the base case is often 0 (because adding zero does not change the result) and when it is *, the base case is 1 (because multiplying by one does not change the result).

This is singly recursive, similar to functions that treat a list as a sequence.

This example takes a single numeric argument.

;; Factorial: n! = n*(n-1)*...*1   from Simply Scheme p. 194
(define (fact n)
  (if (= n 1)
      1
      (* n (fact (- n 1)))))

This example takes two numeric arguments.

;; Like Scheme expt: raise i to the n-th power, i*i*i... n times
(define (pow i n)
  (if (= n 0) 
      1 ; Base case: anything to the zero power is one
      (* i (pow i (- n 1)))))

This example has two recursive calls, but each call makes the number smaller so eventually the function returns.

;; Fibonacci: fib(n) = fib(n-1) + fib(n-2)
(define (fib n)
  (if (< n 2)
      1  ; Base case: fib(1) or fib(0) is 1
      (+ (fib (- n 1))
         (fib (- n 2)))))

This example shows how to use recursion to build up a list. Each recursive call uses append to add an element to the end of the list. However the numeric argument n gets smaller on each recursive call so the function can return.

;; Countup - put a list of numbers from 1 to n in ascending order on front of lst
;; to get just a list from 1 to n, do (countup n '())
(define (countup n lst)
  (if (< n 1)
      lst
      (append (countup (- n 1) lst) (list n))))

Jon Jacky, jackyj@evergreen.edu