Com S 227 --- Introduction to Computer Programming THE RECURSIVE MOMENT (HANDOUT) Copyright (c) 1993 Gary T. Leavens. All rights reserved. Compare the following with section 4.2 in Springer and Friedman's book. Note: there are blanks below. These are for students to fill in. REMOVE-2ND Many students have a misconception about what such a problem involves, in part due to its name. Despite the name, remove-2nd, this procedure, like any other in this part of the course, cannot change any list. So the following picture of what remove-2nd does, crossing out the second cat, removing it from the list, is *wrong*. --------------------\-/-------- my | cat | loves | cXt | food ] NOT! --------------------/-\-------- You cannot change a list in Scheme. Instead, what you can do is to produce an entirely new list, that bears a certain relationship to the argument list. I do find it helpful to draw pictures of this, but the picture has to show both the input (ls) and the output (answer-1): ------------------------------- ls --> my | cat | loves | cat | food ] ------------------------------- | | | (remove-2nd 'cat ls) | v ------------------------- answer-1 --> my | cat | loves | food ] ------------------------- picture 1 Note that answer-1 is a new list, that resembles ls in a certain way. Now here comes the essential idea for recursion, I draw another picture for ``the rest of the journey''; that is, for the recursion: -------------------------- (cdr ls) --> cat | loves | cat | food ] -------------------------- | | | | v --------------------- answer-2 --> cat | loves | food ] --------------------- picture 2 How did we get from (cdr ls) to answer-2 in picture 2? Well, we could go through the same process again, but that doesn't get us anywhere. Instead, we get to use remove-2nd as a helping procedure, and assume that it works! This is recursion. So picture 2 is accomplished by (remove-2nd 'cat (cdr ls)) I am looking to find this when thinking about recursion, so this shouldn't be surprising. Incidentally, you can think of answer-2 in picture 2 as either as the result of (remove-2nd 'cat (cdr ls)) or as (cdr answer-1). Now how to make answer-2 (the result of the rest of the journey) into answer-1? That is, how do we get answer-1 from answer-2? So we can write: (cons (car ls) (remove-2nd 'cat (cdr ls))) for answer-1. That is one case of remove-2nd. Note that the symbol we were to remove was *not* in (car ls). If we look at the problem, we can see that we need to do something else. So let's consider the case where (car ls) is equal to what we want to remove. ------------------------------------------ ls --------> cat | loves | cat | food | for | a | cat ] ------------------------------------------ | | | (remove-2nd 'cat ls) | v ------------------------------------- answer-1 --> cat | loves | food | for | a | cat ] ------------------------------------- picture 3 Again we consider the rest of the journey. We draw the picture for the cdr of this ls (in picture 3): ------------------------------------ (cdr ls) -------> loves | cat | food | for | a | cat ] ------------------------------------ | | | | v answer-2 -----------> ?????? But should we write for answer-2? There are at least two candidates: 1. (remove-2nd 'cat (cdr ls)) 2. (cdr answer-1) In some problems you may need to think about other candidates for answer-2, but these are good ones to try. Indeed alternative 1 should be thought of as the default and only abandoned when it doesn't work. So let's try it. The value of alternative 1 would be the list (loves cat food for a) but it would be hard to get answer-1 from that (although there might be a way). Since that's not simple, we try the second alternative. This makes the following picture. ------------------------------------ (cdr ls) -------> loves | cat | food | for | a | cat ] ------------------------------------ | | | | v ----------------------------- answer-2 -------------> loves | food | for | a | cat ] ----------------------------- picture 4 How do we get from (cdr ls) to answer-2 in this case? We can't use remove-2nd; it doesn't do the right thing. What will do it? It may help to look at similar examples. (For lack of space, I'll not do that here.) Now how do we get answer-1 from answer-2? So we write: (cons (car ls) (remove-1st 'cat (cdr ls))) This exhausts the cases, except the base case (for the empty list). But that's easy. So we can now write our procedure. We have to remember to change the 'cat in our examples to a variable: (DEFINE remove-2nd (LAMBDA (item ls) (COND [(null? ls) '()] [(not (eq? item (car ls))) ; picture 1 (cons (car ls) (remove-2nd item (cdr ls)))] [ELSE ; picture 3 (cons (car ls) (remove-1st item (cdr ls)))]))) or (DEFINE remove-2nd (LAMBDA (item ls) (COND [(null? ls) '()] [(eq? item (car ls)) ; picture 3 (cons (car ls) (remove-1st item (cdr ls)))] [ELSE ; picture 1 (cons (car ls) (remove-2nd item (cdr ls)))]))) LAST-ADJ-OUT (REMOVE-LAST) There are many ways to think about this problem. One way is to see that you can reverse the list, take the first occurrence of the adjective out of the reversal, and then reverse that. It's possible to find this by thinking of symmetry considerations, but that seems a bit magical. Instead I'll show you how to invent another solution, in the same way we worked problem 16. Let's picture an example. --------------------------------------------------- ls --> the | nice | dog | saw | the | nice | nice | puppy ] --------------------------------------------------- | | | (last-adj-out 'nice ls) | v ---------------------------------------------- answer-1 --> the | nice | dog | saw | the | nice | puppy ] ---------------------------------------------- picture 5 The rest of the journey is pictured as follows. ---------------------------------------------- (cdr ls) --> nice | dog | saw | the | nice | nice | puppy ] ---------------------------------------------- | | | (last-adj-out 'nice (cdr ls)) | v ---------------------------------------- answer-2 --> nice | dog | saw | the | nice | puppy ] ---------------------------------------- picture 6 Here answer-2 is the recursive call on the cdr and also (cdr answer-1). How do we get to answer-1 from answer-2? So we can write for this case: Now let's consider the case where (car ls) is the symbol we are looking for. There are two cases. Here is the first case: ---------------------------------------------- ls --> nice | dog | saw | the | nice | nice | puppy ] ---------------------------------------------- | | | (last-adj-out 'nice ls) | v ----------------------------------------- answer-1 --> nice | dog | saw | the | nice | puppy ] ----------------------------------------- picture 7 What is the picture for the rest of the journey? (for (cdr ls)) How do you get from (cdr ls) to answer-2? How do you get answer-1 from answer-2? So we write for this case: Here is the second case where nice is in (car ls). -------------- ls --> nice | puppy ] -------------- | | | (last-adj-out 'nice ls) | v ------- answer-1 --> puppy ] ------- picture 9 What is the picture for the rest of the journey? (for (cdr ls)) How do you get from (cdr ls) to answer-2? How do you get answer-1 from answer-2? So we write for this case: Now put the whole thing together into a program, remembering that you still have to treat the case where ls is null, and change the variables. The problem will be to distinguish the cases given in pictures 7 and 9. How can we distinguish pictures 7 and 9? That is, what is the difference between the following? ---------------------------------------------- ls --> nice | dog | saw | the | nice | nice | puppy ] ---------------------------------------------- -------------- ls --> nice | puppy ] -------------- In both cases, (car ls) is the same, so that doesn't help. But in the first case (picture 7), the nice that is (car ls) is not the last occurrence of nice in the list. Why not? Because there are other occurrences in (cdr ls). We want a procedure that returns a boolean (#t or #f) to answer this question for us. What is the exact problem it should solve? At this point there are two options, we could write a procedure to take the lists (nice ...) and tell if there is another nice in the cdr, or we could write a procedure that takes the cdr and tells whether there is an occurrence of nice in the cdr. Let's try the first one. It's useful to set this up as a problem to be solved explicitly. (occurs-in-cdr 'nice '(nice dog saw the nice nice puppy)) ==> #t (occurs-in-cdr 'nice '(nice puppy)) ==> #f (occurs-in-cdr 'nice '(nice nice puppy)) ==> #t (occurs-in-cdr 'nice '(x)) ==> #f So we can write: ---------------------------------------------- ls --> nice | dog | saw | the | nice | nice | puppy ] ---------------------------------------------- | | | (occurs-in-cdr 'nice ls) | v answer-1 ----------> #t Let's look at the recursion (what happens to (cdr ls)). We'll just consider using the recursion to produce answer-2 ----------------------------------------- (cdr ls) --> dog | saw | the | nice | nice | puppy ] ----------------------------------------- | | | (occurs-in-cdr 'nice (cdr ls)) | v answer-2 ----------> #t How do we get #t from #t? We do nothing. That works in this case, but what if our example was: --------------------- ls --> nice | nice | puppy ] --------------------- | | | (occurs-in-cdr 'nice ls) | v answer-1 ----------> #t We're just considering using the recursion to produce answer-2, so we have: ------------- (cdr ls) --> nice | puppy ] ------------- | | | (occurs-in-cdr 'nice (cdr ls)) | v answer-2 ----------> #f So we can't use (occurs-in-cdr 'nice (cdr ls)) because we want to get #t, and this gives #f. We could try using not to get answer-1 from answer-2, but then that wouldn't work for the case above. So we're stuck. What to do when you're stuck like this? You could bludgeon ahead, and figure out that you can have occurs-in-cdr first call cdr and then call member? (define occurs-in-cdr (lambda (item ls) (member? item (cdr ls)))) But you might not think of that. What else can you do? I'd try again. That is, I'd think about other ways to solve what we need to solve, which is *not* how to write occurs-in-cdr, but ultimately to write last-adj-out. So we need to find a procedure that will tell us the difference between the following: ---------------------------------------------- ls --> nice | dog | saw | the | nice | nice | puppy ] ---------------------------------------------- -------------- ls --> nice | puppy ] -------------- Think about what makes the nice in the first of these not the last and what makes the nice in the second the last. It's what's in the cdr of ls in each case. So we want to test something about the rest of these lists. You might write yourself out an example for this subproblem: (member? 'nice '(dog saw the nice nice puppy)) ==> #t (member? 'nice '(puppy)) ==> #f (member? 'nice '()) ==> #t Now you can either solve this or remember that it is in the book.