anchor.diff:    ;; TODO:  in write up, write this first then the functions
     (dolist (category (categories))
       (let ((contenders (category-titles ballots category))
  -          (title-lists (ballots->title-lists ballots category)))
  +          (category-ballots (category-ballots ballots category)))
         (while (> (length contenders) +maximum-results+)
  -        (let* ((scores (score contenders title-lists))
  +        (let* ((scores (score contenders category-ballots))
                  (least-popular (selection-phase scores))
                  (eliminated (elimination-phase least-popular scores)))
             (setf contenders (set-difference contenders eliminated))
  -          (setf title-lists (eliminate eliminated title-lists))
  +          (setf category-ballots (eliminate eliminated category-ballots))
             (unless (> (length contenders) +maximum-results+)
               (category-eph-results category
  -                                  (score contenders title-lists))))))))
  +                                  (score contenders category-ballots))))))))
   
   ;; To generate the results under the E Pluribus Hugo rules, evaluate
   ;;   (eph-rule-results *ballots*)
  -
  
positive.code:               (fraction->string (second result))
                (third result)))))
  
  (defconstant +maximum-results+ 5)
  
  (defun eph-rule-results (ballots)
  
negative.code:         ((eq line 'eof) ballots)
        (let* ((fields (parse-fields line))
               (voter (first fields))
               (category (parse-integer (second fields)))
               (title (canonical-string (third fields)))
               (ballot (find-ballot voter ballots)))
          (when (null ballot)
            (setf ballot (make-ballot voter))
            (push ballot ballots))
          (add-nomination ballot category title)))))
  
  (defconstant +1984-data-file+
    "/Users/Patrick/projects/e-pluribus-hugo/catsort.txt")
  
  (defparameter *ballots* (file->ballots +1984-data-file+))
  
  (defun category-ballots (ballots category)
    "Return a list of lists containing the votes for works in CATEGORY
    across all BALLOTS."
    (let ((category-ballots (list)))
      (dolist (ballot ballots category-ballots)
        (let ((works (gethash category (cdr ballot))))
          (unless (null works)
            (push works category-ballots))))))
  
  (defun category-votes (ballots category)
    "Return a list of lists with each inner list containing the title of a
    nominated work and the number of ballots on which it appeared."
    (let ((votes (list)))
      (dolist (ballot (category-ballots ballots category) votes)
        (dolist (work ballot)
          (let ((vote (find-if (lambda (vote)
                                 (string= (car vote) work))
                               votes)))
            (if (null vote)
                (push (list work 1) votes)
                (incf (second vote))))))))
  
  (defun current-rule-results (ballots)
    "Return the list of nominated works in each category that result from
    applying the current rules to the BALLOTS."
    (dolist (category (categories))
      (let ((sorted-results (sort (category-votes ballots category)
                                  #'> :key #'second)))
        (format t "~A~%" (category-description category))
        (dolist (result sorted-results)
          (when (>= (second result) (second (sixth sorted-results)))
            (format t "~A:  ~A~%" (first result) (second result)))))))
  
  ;; To generate the results under the current rules, evaluate
  ;;   (current-rule-results *ballots*)
  
  
  (defun category-titles (ballots category)
    "Return a list of all unique titles (or authors) in the CATEGORY."
    (remove-duplicates
     (reduce #'append (category-ballots ballots category))
     :test 'string=))
  
  (defun score (contenders category-ballots)
    "Calculate the points and ballot counts for each title or author in
    CONTENDERS."
    (let ((scores (list)))
      (dolist (title contenders scores)
        (let ((points 0)
              (ballots 0))
          (dolist (ballot category-ballots)
            (when (find title ballot :test 'string=)
              (incf points (/ 1 (length ballot)))
              (incf ballots)))
          (push (list title points ballots) scores)))))
  
  