Problem set 4 solutions		1


			   Solutions to Problem Set 4
				6.001  Fall 90
			Solutions to Homework Exercises

Exercise 1.

Here is a transcript showing what the Scheme interpreter would print.  (Note
that this is the MacScheme interpreter, so minor things like the appearance of
the prompts may be different.)

>>> (define a (list 1 2))
a
>>> (define b (list 3 4 5))
b
>>> (cons a b)
((1 2) 3 4 5)
>>> (append a b)
(1 2 3 4 5)
>>> (list a b)
((1 2) (3 4 5))

Exercise 2.

>>> (list a 'b c 'd)
(4 b 2 d)
>>> (list (list a) (list b))
((4) (3))
>>> (cons (list d) 'b)
((1) . b)
>>> (cadr '((a b) (c d) (a d) (b c)))
(c d)
>>> (cdr '(a))
()
>>> (atom? (caddr '(Welcome to MIT)))
#t
>>> (memq 'sleep '(where getting enough sleep))
(sleep)
>>> (memq 'time '((requires good) (management of time)))
()

Exercise 3.

The straightforward definition of equal?  (called my-equal?  here so I don't
redefine the equal?  predicate built in to Scheme) is as follows:

>>> (define my-equal? 
      (lambda (x y)
        (cond ((atom? x)
               (cond ((atom? y) (eq? x y))
                     (else false)))
              ((atom? y)
               false)
              (else (if (my-equal? (car x) (car y))
                        (my-equal? (cdr x) (cdr y))
                        false))))) 

We check for equality of atoms by eq?, and of everything else by recursively
checking for equality of their parts (car and cdr).  If x is an atom, y can be
equal to it only if it is itself an atom.  If we reach the second clause of the
outer cond, then we know that x was not an atom but y was, so they cannot be
equal.  Finally, in the third clause if the two car's aren't equal, then x and
y can't be, whereas if they are, then x and y are equal just in case their
cdr's are.

An example is:
>>> (my-equal? '(a b c) '(a b c))
#t

To handle the case of numbers (for which eq?  is not appropriate), we could
augment our definition of my-equal?  to be the following:

>>> (define my-equal? 
      (lambda (x y)
        (cond ((number? x)
               (if (number? y)
                   (= x y)
                   false))
              ((number? y)
               false)
              ((atom? x)
               (cond ((atom? y) 
                      (eq? x y))
                     (else false)))
              ((atom? y)
               false)
              (else (if (my-equal? (car x) (car y))
                        (my-equal? (cdr x) (cdr y))
                        false))))) 

Note that this shows correct behavior on the following few samples:

>>>  (my-equal? 3 4)
()
>>> (my-equal? true 5)
()
>>> (my-equal? 5 true)
()
>>> (my-equal (+ 1 3) 4)
#t

In fact, however, Scheme provides a built-in predicate that does the right
thing with atoms and numbers, called eqv?.  Thus, instead of our second
definition, we could simply replace the use of eq?  in the first by eqv?  and
have it work correctly.  This is because eqv?  essentially includes the
case-split we have programmed in our second version, treating specially the
special case of atoms that happen to be numbers.

Exercise 4.
>>> (member '(great fun) '(spelunking is great fun))
()
>>> (member '(great fun) '((spelunking is) (great fun) (so they say)))
((great fun) (so they say))
>>> (member '(great fun) '((spelunking is) ((great fun) (so they say))))
()

Exercise 5.

>>> (define filter
      (lambda (predicate list)
        (if (null? list)
            '()
            (if (predicate (car list))
                (cons (car list) 
                  (filter predicate (cdr list)))
                (filter predicate (cdr list))))))
filter
>>> (filter odd? '(1 2 3 4 5 6 7 8))
(1 3 5 7)

Exercise 6.

Here we use the dotted-tail notation to bind all but the first argument to l.
Then, the procedure we pass to filter l is simply odd?  if the first argument
was odd, and even?  (in its spelled-out version) otherwise.

>>> (define (same-parity x . l)
      (filter (if (odd? x) odd? (lambda (x) (not (odd? x))))
              l))
same-parity
>>> (same-parity 1 2 3 4 5 6 7)
(3 5 7)
>>> (same-parity 2 3 4 5 6 7)
(4 6)


Solutions to Laboratory Problems on Problem Set 4.

The program that you have been working on here is a modern implementation of a
famous old program from the 1960's, called Eliza, written by Joe Weizenbaum.
That program mimicked the behavior of a non-directive psychotherapist by keying
on features of the user's input to select its next response.  For example, if
you typed "I wish I knew my father better", Eliza might respond with something
like "Tell me more about your family", keyed by the word "father".  It could
also perform the change-person transformation to reflect back to the user what
he or she had just said.  The program was technically interesting for its
relatively interesting behavior based on a very simple set of mechanisms, for
its implementation in a set of list-processing extensions to FORTRAN, and
(mostly) for the fact that Weizenbaum became distressed at how willing users
appeared to be to take seriously their conversations with what was ultimately a
very shallow, unthinking and unfeeling "therapist." His later work arguing
against the spread of artificial intelligence was probably motivated by these
observations.  See Weizenbaum, J.,Computer Power and Human Reason, W.  H.
Freeman, 1976.

Problem 4.1

After we define the following accessors and repeat our definition of filter,

(define (entry-keys x) (car x))
(define (entry-value x) (cadr x))

(define filter 
  (lambda (predicate list)
    (if (null? list)
      '()
      (if (predicate (car list))
          (cons (car list)
                (filter predicate (cdr list)))
          (filter predicate (cdr list))))))

we can have a conversation like the following:

>>> (see-advisor 'pete)

(hi pete)
(i am your freshman advisor)
(what are your plans for the semester)
**(I want to take 18:03 and physics)
(you have to take 18:02 first)
**(Ok, 18:02 and physics)
(you have to take 18:01 first)
**(Ok, what about physics)
(students really enjoy 8:01)
**(I was also thinking of taking 6:001)
(6:001 is too much work for freshmen -- wait until next year)
**(Ok)
(make sure to get time to explore the boston area)
**(I don't have time, but I was thinking of taking a freshman seminar)
(i hear that snorkeling in boston harbor is a really exciting seminar)
**(I think that sounds like fun)
(make sure to get time to explore the boston area)
**(You said that already)
(mit has a lot of interesting departments)
**(I know it does)
(have you considered a context subject)
**(Yes, but I don't think I will take one now)
(mit has a lot of interesting departments)
**(You sure are repetitive)
(why do you say you sure are repetitive)
**(Because you are, dear advisor)
(i am glad to hear that because you are, dear advisor)
**(goodbye)
(goodbye pete)
(have a good semester!)
#t

Note: what you get may be a bit different, because at times the advisor's
selection was made at random.  Also, these solutions were generated in
MacScheme, whose typographic conventions are slightly different from Scheme's
in the 6.001 lab.  For example, output is not automatically put into
upper-case.

Problem 4.2.

We hope you had fun with this.  There was nothing to turn in for it.

Problem 4.3.

Because the abstractions were reasonably well selected in the original problem
set code, making the required change to allow multiple bits of advice attached
to each keyword is straightforward.  It requires changing only the following:

(define (make-entry keys . values)
  (cons keys values))

(define (entry-value x)
  (pick-random (cdr x)))

To test the change, here is a new definition of conventional-wisdom, in which
6:001 has three possible bits of associated advice.  This is followed by an
unimaginative transcript that shows the correct resulting behavior.

(define conventional-wisdom
  (list
   (make-entry
    '(6:001)
    '(6:001 is too much work for freshmen -- wait until next year)
    '(computers are a waste of time)
    '(why don't you take 1:00 instead))
   (make-entry
    '(8:01 physics)
    '(students really enjoy 8:01))
   (make-entry
    '(seminar seminars)
    '(i hear that snorkeling in Boston Harbor is a really exciting seminar))
   ))

>>> (see-advisor 'pete)

(hi pete)
(i am your freshman advisor)
(what are your plans for the semester)
** (6:001) 
(why don't you take 1:00 instead)
** (6:001)
(6:001 is too much work for freshmen -- wait until next year)
**(6:001)
(why don't you take 1:00 instead)
**(6:001)
(computers are a waste of time)
**(6:001)
(6:001 is too much work for freshmen -- wait until next year)

Note that you were asked to do only one of problems 4.4 and 4.5.  The staff are
not so lucky.  Solutions to both follow:

Problem 4.4

The prerequisite relations here form a tree, in which each subject has
descendants that are its prerequisites.  The first-prerequisite of a subject
with no prerequisites is nothing.  If there are prerequisites, we want to chase
down all the prerequisites of those, until we find ones with no further
prerequisites of their own.  We collect all these into a list, from which we
could either select one to complain about or (as here) we give the whole list.

(define (first-prerequisites subject)
  (ultimate-prerequisites '() (lookup subject prerequisite-structure)))

(define (ultimate-prerequisites ancestor-list descendants)
  (if (null? descendants)
      ancestor-list
      (map-append (lambda (x) 
                    (ultimate-prerequisites
                      (list x) 
                      (lookup x prerequisite-structure)))
                  descendants)
      ))

(define (map-append function list)
  (if (null? list)
      '()
      (append (function (car list))
              (map-append function (cdr list)))))

Algorithm: First-prerequisites invokes ultimate-prerequisites with a list of
the immediate prerequisites of the given subject (and with () to return in case
there are no prerequisites).  Ultimate-prerequisites appends together the list
of ultimate prerequisites of each of the descendants.  The function applied to
each descendant by map-append is just like first-prerequisite except that a
descendant that has no prerequisite is added to the list.

With the given prerequisites:

(define prerequisite-structure  
  (list (make-entry '(6:003) '(6:001 6:002))
        (make-entry '(6:002) '(18:03 8:02))
        (make-entry '(8:02) '(8:01))
        (make-entry '(18:03) '(18:02))
        (make-entry '(18:02) '(18:01))))

we get the following:

>>> (first-prerequisites '6:001)
()
>>> (first-prerequisites '6:003)
(6:001 18:01 8:01)
>>> (first-prerequisites '18:03)
(18:01)
>>> (first-prerequisites '6:002)
(18:01 8:01)

To integrate this into the advisor, we need only change the definition of
check-prerequisite.  Note that we actually need not call first-prerequisites,
which we defined, because find-response already starts us off with the
immediate prerequisites of the subject that was mentioned.

(define (check-prerequisite input)
  (let ((prereq (ultimate-prerequisites 
                 '() 
                 (find-response prerequisite-structure input))))
    (if (null? prereq)
        false
        (append '(you must start by taking the following first:) prereq))))

The following shows the resulting interaction with the advisor:

>>> (see-advisor 'pete)

(hi pete)
(i am your freshman advisor)
(what are your plans for the semester)
**(I want to take 6:003)
(you must start by taking the following first: 6:001 18:01 8:01)
**(I want to take 6:001)
(6:001 is too much work for freshmen -- wait until next year)
**(I want to take 6:002)
(you must start by taking the following first: 18:01 8:01)
**(I want to take 8:02 and 18:03)
(you must start by taking the following first: 8:01)
**(I want to take 18:03)
(you must start by taking the following first: 18:01)

Progress here is incremental and slow.  Thus, although we have improved the
code at least to tell us the ultimate prerequisites of a subject we are
interested in, the next-to-last interaction points out that the system still
responds only partly to our needs; note that 18.03 was ignored (as before)
because the response was keyed by 8.02, which appeared earlier in the input.


Problem 4.5.

The issue here is to build a matcher that tries to find one sequence of atoms in another:

(define (subsequence? s1 s2)
  (cond ((null? s1) true)
        ((null? s2) false)
        ((matches? s1 s2))
        (else (subsequence? s1 (cdr s2)))))

(define (matches? s1 s2)
  (cond ((null? s1) true)
        ((null? s2) false)
        ((eq? (car s1) (car s2))
         (matches? (cdr s1) (cdr s2)))
        (else false)))

Subsequence works by trying to line up the list of words in s1 against
successive sublists of s2, until it either succeeds or runs off the end of s2.
Matches?  determines whether a particular alignment is a match or not, by
checking if the two aligned parts are eq?  word-by-word.  Note that if the test
sequence runs out, we have a successful match, but if the sequence against
which we're testing runs out, the match has failed.  This type of operation is
common in may text- processing programs, and there are many optimizations
possible, which we have ignored.  For example, if a match fails because s1 is
longer than what is left of s2, subsequence?  is guaranteed to lose for all
subsequent alignments of s1 against tails of s2.  We can test these out:

>>> (subsequence? '(double major) '(I was thinking of a double major in computer 
science and urban studies))
#t
>>> (subsequence? '(major in computer science) '(I was thinking of a double 
major in computer science and urban studies))
#t
>>> (subsequence? '(major in english) '(I was thinking of a double major in 
computer science and urban studies))
()

To make this work with the advisor, we must change the way that find-response
works, because it is no longer adequate to look at the input one word at a
time.  Instead, pass the complete input to lookup and use find-matching-entry
to find an entry that matches somewhere in the input.  In order to continue to
support single-word matches, we test in find-matching- entry to see whether the
key under consideration is an atom or a list of words; we use the member test
for a single word, and our newly-defined subsequence?  for lists.

(define (find-response entries input)
  (let ((possible-responses
         (lookup entries input)))
    (if (null? possible-responses)
        false
        (pick-random possible-responses))))

(define (lookup entries input)
  (filter (lambda (val) (not (null? val)))
          (mapcar (lambda (entry) 
                    (find-matching-entry
                      (entry-keys entry) input (entry-value entry)))
                  entries)))

(define (find-matching-entry keys input value)
  (cond ((null? keys) '())
        ((if (atom? (car keys))
             (member (car keys) input)
             (subsequence? (car keys) input))
         value)
        (else (find-matching-entry (cdr keys) input value))))

Here is an extension of conventional-wisdom to demonstrate:

(define conventional-wisdom
  (list
   (make-entry
    '((major in course 6) (major in computer science))
    '(course 6 is too crowded -- find something else))
   (make-entry
    '(6:001)
    '(6:001 is too much work for freshmen -- wait until next year))
   (make-entry
    '(8:01 physics)
    '(students really enjoy 8:01))
   (make-entry
    '(seminar seminars)
    '(i hear that snorkeling in Boston Harbor is a really exciting seminar))
   ))

A transcript shows that it works as desired:

>>> (see-advisor 'pete)

(hi pete)
(i am your freshman advisor)
(what are your plans for the semester)
**(I want to major in computer science)
(course 6 is too crowded -- find something else)
**(I want to take 6:001)
(6:001 is too much work for freshmen -- wait until next year)

