A Recursive remove Function

Recap: The remove-first Function

In Session 9, we wrote a function named remove-first that takes two arguments, a symbol s and a list of symbols los. It returns a list just like los minus only the first occurrence of s.

(define remove-first
  (lambda (s los) 
    (if (null? los)
        '()
        (if (eq? (first los) s)
            (rest los)
            (cons (first los)
                  (remove-first s (rest los)))))))

It works:

> (remove-first 'a '(a b c))
'(b c)
> (remove-first 'b '(a b c))
'(a c)
> (remove-first 'd '(a b c))
'(a b c)
> (remove-first 'a '())
'()
> (remove-first 'a '(a a a a a a a a a a))     ; count 'em up!
'(a a a a a a a a a)

For a little practice, let's try a variation of this problem.

The remove Function

What if we wanted to remove all occurrences of a symbol from a list of symbols?

The function remove behaves like remove-first, but it removes all occurrences of the symbol, not just the first. The structure of remove-first and remove are so similar that we can focus on how to modify remove-first to convert it into remove.

In terms of our code, how does the new function differ from remove-first?

So:

(define remove
  (lambda (s los) 
    (if (null? los)                         ; on an empty list, the
        '()                                 ; answer is still empty
        (if (eq? (first los) s)

            ;; WHAT DO WE DO HERE?

            (cons (first los)               ; we still have to preserve
                  (remove s (rest los)))    ; non-s symbols in los
        ))))

In remove-first, as soon as we find s we return the rest of the los, into which are consed any non-s symbols that preceded s in los. But in remove, we need to be sure to remove not just the first s (by returning the rest of los) but all the s's, including any that may be lurking in (rest los). So:

(define remove
  (lambda (s los) 
    (if (null? los)
        '()
        (if (eq? (first los) s)
            (remove s (rest los))           ;; *** HERE IS THE CHANGE! ***
            (cons (first los)
                  (remove s (rest los)))))))

Does it work?

> (remove 'a '(a b c))
(b c)
> (remove 'a '(a a a a a a a a a a))
()

Notice the relationship between the structure of the data and the the structure of our code. The structure of the data did not change from remove-first to remove, so neither did the structure of the function.

A small change in spec resulted in a small change in code.

remove-first and remove demonstrate the basic technique for writing recursive programs based on inductive data specifications. This is a pattern you will find in many programs, both functional and object-oriented.

We call this pattern structural recursion.