;;; ;;; Notes: ;;; in add-prereqs, the requirement was that if course already had prereqs, ;;; the new ones should be added to the old ones. A common mistake here ;;; is to implement add-prereqs by appending the new prereq-list to ;;; the result of a call to prereqs on course. This creates a problem ;;; because it makes the recursive prereqs (i.e. the prereqs of the prereqs) ;;; immediate prereqs of course. For example: ;;; ;;; (add-prereqs 'a '(b)) ;;; (add-prereqs 'b '(c)) ;;; (prereqs 'a) => (b c) ;;; (add-prereqs 'a '(d)) ;;; (prereqs 'a) => (b c d) ;;; (clear-prereqs 'b) ;;; (prereqs 'a) => (b d) ;;; ;;; The last call to prereqs woudl have returned (b c d) if add-prereqs ;;; had been implemented using a call to prereqs, instead of simply ;;; appending the immediate prereqs to the new ones. See the implementations. ;;; ;;; Checking for cycles must include the recursive check. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; ASSOC version. Courses are stored with their immediate prereqs as: ;;; ((course1 pre1 pre2 pre3 ...) ;;; (course2 pre1 pre2 ...) ;;; ...) ;;; ;;; Advantage: uses only one variable from the global space *pre-list* ;;; Disadvantage: Assoc is fairly slow (linear search), more code than ;;; the other approaches, clearing and adding additional prereqs are ;;; a bit cumbersome, also on a second add-prereq for the same course, ;;; rather than remove the old prereq, this justs adds a new version ;;; to the beginning of the alist which wastes a little space, but ;;; the assoc never notices since it stops at the first one it finds. (defvar *pre-list* nil) (defun prereqs (course) ;; use a let to avoid calling assoc twice (let ((immed-prereqs (cdr (assoc course *pre-list*)))) (remove-duplicates (append immed-prereqs (mapcan #'prereqs immed-prereqs))))) ;;; Note there is no need to remove duplicates here since ;;; it is done in the prereqs function. (defun add-prereqs (course plist) (cond ((null plist) plist) ((cycle-check course plist) (error "Cycle detected")) ;; the error function raises an error ;; note that in the case below, we can't append plist to ;; (prereqs course) - see note above. ;; (cdr (assoc course plist)) is the immediate prereqs ((setq *pre-list* (cons (cons course (append plist (cdr (assoc course plist)))) *pre-list*)) plist))) (defun cycle-check (course plist) (or (member course plist) (member course (mapcan #'prereqs plist)))) (defun clear-prereqs (course) (setq *pre-list* (remove-if #'(lambda (x) (eq (car x) course)) *pre-list*))) #| ;;An alternative clear-prereqs: (defun clear-prereqs (course) (remove course *pre-list* :test #'(lambda (x y) (eq (car y) x)))) |# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; SET version. Immediate prereqs are stored as the symbol-value ;;; of the course name. ;;; ;;; Advantages: Fast. Elegant and short code. No wasted space. ;;; Disadvantages: Uses many symbols from the global namespace which can ;;; not be used for anything else. ;;; (defun prereqs (course) (if (boundp course) ; guard the eval ;; use a let to avoid doing two evals. (let ((immed-prereqs (eval course))) (remove-duplicates (append immed-prereqs (mapcan #'prereqs immed-prereqs)))))) (defun add-prereqs (course plist) (if (cycle-check course plist) (error "Cycle detected. You are a moron.") ;; Note that the append below can not be to (prereqs course) ;; see note above, we must append to the immediate prereqs only. (set course (append plist (if (boundp course) ; guard the eval (eval course)))))) ;; Note that this is the same as in the alist version (defun cycle-check (course plist) (or (member course plist) (member course (mapcan #'prereqs plist)))) (defun clear-prereqs (course) (set course nil)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Property version ;;; ;;; Advantages: As fast and elegant as SET version, perhaps ;;; faster since it does not use eval. Does not ;;; use any global namespace symbols except to assign them ;;; a property 'prereqs They can still be used as variables. ;;; (defun prereqs (course) (let ((immed-prereqs (get course 'prereqs))) (if immed-prereqs ; note that this if isn't stricly needed since (append nil nil) => nil (remove-duplicates (append immed-prereqs (mapcan #'prereqs immed-prereqs)))))) (defun add-prereqs (course plist) (if (cycle-check course plist) (error "Cycle detected. You are a moron.") ;; Note that the append below can not be to (prereqs course) ;; see note above, we must append to the immediate prereqs only. (setf (get course 'prereqs) (append plist (get course 'prereqs))))) ;; Note that this is the same as in the alist version (defun cycle-check (course plist) (or (member course plist) (member course (mapcan #'prereqs plist)))) (defun clear-prereqs (course) (setf (get course 'prereqs) nil))