;;
;; PS5 Solutions -- Building a Freshman Advisor.
;;
;; M. J. Hawley (mike@media-lab.mit.edu)
;; working with D. Hwang (debbieh@kaon.LCS.MIT.EDU)
;; and H. Abelson (hal@martigny.ai.mit.edu)
;;
;; (This took us about 4 hours)
;;
;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ANSWERS TO PRELAB EXERCISES

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 1
;;    The difference between list-union and append is that, if an
;;    element appears in both lists, then append will include it twice
;;    in the result, while list union will include it only once.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 2
;;    To compute the product of the numbers in a list of numbers, just
;;    do
    (reduce * 1 list-of-numbers)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 3
;;   The reduce-map combination is a powerful idiom, which
;;   simplifies a lot of list processing.

    (reduce + 0 (map square list-of-numbers))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 4
;;   Reducing with append or with list-union both combine the elements
;;   in a list of lists into a single list.  List-union, however,
;;   won't include an element more than once.


   (reduce append '() '((1 2) (2 3) (4 6) (5 4)))
   ;Value: (1 2 2 3 4 6 5 4)

   (reduce list-union '() '((1 2) (2 3) (4 6) (5 4)))
   ;Value: (1 2 3 6 5 4)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 5
;; A more descriptive name for the procedure PROC would be
;; REMOVE-DUPLICATES.  By mapping LIST along the list of symbols, we
;; generate a list of 1-element lists.  Then we reduce these, using
;; list-union into a single list of elements as in pre-lab exercise 4.



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ANSWERS TO LAB EXERCISES

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 1
;;    Expand 'beginnings' and 'general-advice'
;;    by simply appending some new remarks.
;;
;;    Strings are chosen at random from these lists
;;    (ie, they are simply referenced by 'pick-random')
;;    so we don't need to worry about changing the order
;;    or length of these lists.

(define beginnings
  (append beginnings
    '((why do)
      (what do you mean)
      (i can tell from the look on your face that))))

(define general-advice
  (append general-advice
    '((the museum of science is free for MIT students you know)
      (if you need phys-ed credits, try something fun like sailing or scuba)
      (i hear good things about the human anatomy dissection
       lab at harvard -- you can cross-register, you know))))

;;
;; Some output examples:
;;
;;   **  (i plan to major in sleep)
;;   (i can tell from the look on your face that you plane to major in sleep)
;;   **  (just kidding)
;;   (how about a freshman seminar)
;;   **  (like what)
;;   (i hear good things about the human anatomy dissection lab at harvard -- 
;;   you can cross-register, you know)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exercise 2
;;    Here's an example of input to trigger the rules in 'conventional-wisdom'
;;    and 'subject-knowledge.'  I've underlined the parts of the pattern
;;    that the rule will match: "x======" shows the stuff collected by (?? x),
;;    "y=====" is the stuff collected by (?? y), "s======" is what's matched
;;    by expressions like "(? s ,in-catalog)" and "-----" indicates a
;;    "literal" or token-like part of the rule.
;;
;; conventional-wisdom
;;
;; **  (i am boning up on 6:001)
;;      x================ -----
;;
;; (6:001 is too much work for freshmen -- wait until next year)
;;
;; **  (how about 8:01)
;;      x======== ---- (note the empty list matches y in this rule)
;;
;; (students really enjoy 8:01)
;;
;; **  (maybe a seminar would be even more fun)
;;      x====== ------- y=====================
;;    
;; (i hear that snorkeling in boston harbor is a really exciting seminar)
;;
;; **  (i want to take bobsledding next term)
;;      x===== ------- y========== ---- z===
;;    
;; (too bad -- bobsledding is not offered next term)
;;
;; **  (that will make it hard to finish a double major in gym and engineering)
;;      x================================= --------------- y== --- z==========
;;
;; (gym is fascinating and you can make a living doing it if engineering 
;;     does not work out)
;;
;; **  (but i still want to double major)
;;      x================== ------------
;; 
;; (doing a double major is a lot of work)
;;
;; subject-knowledge
;;
;; **  (what is 5:11 about)
;;      ------- s=== -----
;;
;; (5:11 is about smelly organic crud and goop)
;;
;; **  (what are 8:03 and 8:04 about)
;;      -------- s============ -----
;; 
;; (8:03 is about waves)
;; (8:04 is about quantum wierdness)
;; 
;; **  (how many units is 6:001)
;;      ----------------- s====
;; 
;; (6:001 is a 15 unit subject)


;; **  (how many units are 8:03 and 8:04)
;;      ------------------ s============
;; 
;; (8:03 is a 12 unit subject)
;; (8:04 is a 12 unit subject)
;; 
;; **  (what are the prerequisites for 8:04)
;;      ------------------------------ s===
;; 
;; (the prerequisites for 8:04 are 8:03 18:03)
;; 
;; **  (can i take 8:04)
;;      ---------- s===
;; 
;; (the prerequisites for 8:04 are 8:03 18:03)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 3
;;    'all-prerequisites' recursively finds all the (real) prerequisites
;;    of a subject.  
;;
;;    Note that this implementation assumes that there
;;    are no "loops" implied in the prerequisite lists (that is,
;;    there is no situation in which x is a prereq of y and y is a prereq of x).
;;    (Why?!)
;;
;;    If it's not in the catalog, then we'll assume there are no prerequisites.
;;    Otherwise we take the union of its immediate prerequisites, with
;;    the union of all the prerequisites of its immediate prerequisites.
;;    Notice how we can compute (recursively) all the prerequisites of the
;;    prerequisites by mapping all-prerequisites itself over the list of
;;    immediate prerequisites and using list-union to combine the
;;    resulting list of lists.
;;

(define (all-prerequisites subject)
  (let ((entry (find subject catalog)))
    (if (not entry)
        '()
        (let ((immediate-prerequisites (entry-prerequisites entry)))
          (list-union
           immediate-prerequisites
           (reduce list-union
                   '()
                   (map all-prerequisites immediate-prerequisites)))))))

;;
;; An output sample:
;;
;;  (all-prerequisites '12:004)
;;  ;Value: (18:03 8:02 18:02 8:01 18:01)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 4
;;    Add a new rule to 'subject-knowledge' that answers questions of the form
;;    "Can I take ...if I have not taken ...?"
;;

   (make-rule
    `(can I take (? s1 ,in-catalog) if I have not taken (? s2 ,in-catalog))
    (lambda (dict)
      (let ((wanted-subject (entry-subject (value 's1 dict)))
            (possible-prereq (entry-subject (value 's2 dict))))
        (let ((prereqs (all-prerequisites wanted-subject)))
          (if (memq possible-prereq prereqs)
              (write-line '(nope!))
              (write-line '(yup)))))))

;;
;; An output sample:
;;
;; **  (can i take 6:002 if I have not taken 6:001)
;; 
;; (yup!)
;; 
;; **  (can i take 6:002 if I have not taken 8:02)
;; 
;; (nope!)
;; 
;; **  (can i take 12:004 if I have not taken 8:01)
;; 
;; (nope!)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 5
;;    'check-circular-prerequisites?' takes a list of subjects and checks to
;;    see if any are prerequisites of any other subject.
;;    An EASY way to do this uses the following idea:
;;    Find the union of all of the prerequisites of all of the subjects
;;    in the list.  Then make sure that no subject in the original list is also
;;    in this big list of prerequisites, i.e., that the list-intersection
;;    of these two lists is empty.  Note how easy the problem becomes,
;;    if we rephrase it in terms of operations like union
;;    and intersection and "for all the subjects in the list" (i.e., map).
;;    Without this idea, the code can become much more difficult and
;;    it's easy to get into a rats' nest of recusion scanning through lists.

(define (check-circular-prerequisites? subjects)
  (null? (list-intersection
          subjects
          (reduce list-union                       
                  '()
                  (map all-prerequisites subjects) ;union of all prerequisites of
                  ))))                             ;all subjects in the list


;;
;; Some output cases:
;;
;; (check-circular-prerequisites? '(18:01 12:004))
;;
;; ;Value: #f
;;
;; (check-circular-prerequisites? '(6:001 6:002))
;;
;; ;Value: #t
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 6
;;    'total-units' takes a list of subjects and returns the
;;    total number of units of all subjects in the list.
;;    This is easy with reduce and map.  To make things a little more
;;    robust, we return 0 units for a subject that is not in the
;;    catalog.

(define (total-units subjects)
  (define (units subject)
    (let ((entry (find subject catalog)))
      (if (null? entry)
          0
          (entry-units entry))))
  (reduce + 0 (map units subjects)))

;;
;; Sample output:
;;
;; (total-units '(6:001 18:01 8:01))
;;
;; ;Value: 39
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 7
;;    'check-subject-list' takes a list of subjects and
;;    checks it for circular prerequisites and exceeding
;;    the freshman limit (54 units).  This is straightforward
;;    since we've already abstracted the necessary predicates.
;;    Again, we've made this more robust by returning '() for subjects
;;    that are not in the catalog.

(define freshman-limit 54)

(define (check-subject-list subjects)
  (let ((p (reduce list-union
                   '()
                   (map 
                    (lambda (s)
                      (let ((entry (find s catalog)))
                        (if (null? entry)
                            '()
                            (entry-prerequisites s))))
                    subjects))))
    (cond ((not (check-circular-prerequisites? subjects))
           (write-line
            '(not so fast!  there are circular prerequisites in that list!)))
          ((> (total-units subjects) freshman-limit)
           (write-line
            (append 
             '(that is too much work:)
             (list (total-units subjects))
             '(units -- the maximum is only)
             (list freshman-limit))))
          ((> (length p) 0)
           (write-line
            (append 
             '(you need the following prerequisites:) p)))
          (else (write-line '(ok!))) )))


;; Sample output:
;;
;; (check-subject-list '(18:01 12:004))
;; 
;; (not so fast! there are circular prerequisites in that list!)
;; ;Value: #[undefined-value]
;; 
;; (check-subject-list '(6:002))
;; 
;; (you need the following prerequisites: 8:02 18:02 8:01 18:01)
;; ;Value: #[undefined-value]
;; 
;; (check-subject-list '(6:001 6:001 6:001 6:001 6:001))
;; 
;; (that is too much work: 75 units -- the maximum is only 54)
;; ;Value: #[undefined-value]
;; 
;; (check-subject-list '(6:001 8:01 18:01))
;; 
;; (you need the following prerequisites: true-grit)
;; ;Value: #[undefined-value]
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 8
;;    Add a new rule to subject-knowledge for which the pattern is
;;      `(I want to take (?? s ,subjects))
;;
;;    As in Exercise 4, this rule can be inserted into the 'subject-knowledge' list.
;;    Notice how we use map to compute the list of subject names from
;;    the catalog entries returned by the matcher.

   (make-rule
     `(i want to take (?? s ,subjects))
     (lambda (dict) (check-subject-list (map entry-subject (value 's dict)))))

;;
;; Sample output:
;;
;; (see-advisor 'mike)
;;
;; (hi mike)
;; (i am your freshman advisor)
;; (what are your plans for the semester)
;; 
;; **  (pursuing amateur zymurgy)
;; 
;; (make sure to take some humanities)
;; 
;; **  (i want to take 6:001 and 6:002)
;; 
;; (you need the following prerequisites: true-grit 8:02 18:02)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Exercise 9
;;     This is a free-wheeling exercise -- you're on your own with it,
;;     but we'll post some of the most interesting solutions.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
