We just 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)
A quick exercise: before moving forward: Suppose that, instead of
(cons (first los) (remove-first s (rest los)))as the 'else' clause of the second if, we had just
(remove-first s (rest los))What function would remove-first then compute?
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))))))) > (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.