anchor.diff:             collect (cons language (load-model model))))
      t))
   
  -(defun classify (input &key (ngram-limit 400)
  -                            (languages *language-models*)
  -                            remove-singletons
  -                            (cutoff 1.05)
  -                            (sample-size 20000))
  -  "Return the language of INPUT.
  -
  -LANGUAGES is the alist of language models to use; default is
  -*LANGUAGE-MODELS*.
  -
  -SAMPLE-SIZE limits the portion of INPUT used to generate the ngram
  -profile to a certain number of characters (default is 20,000, which is
  -plenty). NIL means no limit.
  -
  -REMOVE-SINGLETONS is either NIL (don't), T (always), or a
  -number (above this length). It provides a slight increase in speed for
  -long texts, but should not be used with short texts.
  -
  -If a guess is unlikelier than than CUTOFF times the previous guess, it
  -is discarded."
  +(defun input-model (input &key (sample-size *sample-size*)
  +                               remove-singletons)
  +  (check-type input string)
     (let* ((sample
              (if (or (not sample-size)
                      (> sample-size (length input)))
                  input
                  (nsubseq input 0 sample-size)))
  -         (unknown
  -           (let ((lm (create-lm sample
  -                                :remove-singletons
  -                                (if (numberp remove-singletons)
  -                                    (> (length sample) remove-singletons)
  -                                    remove-singletons))))
  -             (map-into lm #'car lm)))
  +         (model (create-lm sample
  +                           :remove-singletons
  +                           (if (numberp remove-singletons)
  +                               (> (length sample) remove-singletons)
  +                               remove-singletons))))
  +    (map-into model #'car model)))
  +
  +(defun language-score (unknown model &optional (ngram-limit *ngram-limit*))
  +  (loop for i from 0
  +        for ngram in unknown
  +        for match = (gethash ngram model)
  +        if match
  +          sum (abs (- match i))
  +        else sum ngram-limit))
  +
  +(defun classify (input &key (ngram-limit *ngram-limit*)
  +                            (languages *language-models*)
  +                            remove-singletons
  +                            (cutoff *cutoff*)
  +                            (sample-size *sample-size*))
  +  "Return the language of INPUT.
  +
  +LANGUAGES is the alist of language models to use; default is
  +*LANGUAGE-MODELS*.
  +
  +SAMPLE-SIZE limits the portion of INPUT used to generate the ngram
  +profile to a certain number of characters (default is 20,000, which is
  +plenty). NIL means no limit.
  +
  +REMOVE-SINGLETONS is either NIL (don't), T (always), or a
  +number (above this length). It provides a slight increase in speed for
  +long texts, but should not be used with short texts.
  +
  +If a guess is unlikelier than than CUTOFF times the previous guess, it
  +is discarded."
  +  (let* ((unknown (input-model input
  +                               :sample-size sample-size
  +                               :remove-singletons remove-singletons))
            (distances
              (loop for (language . model) in languages
                    collect (cons language
  -                               (loop for i from 0
  -                                     for ngram in unknown
  -                                     for match = (gethash ngram model)
  -                                     if match
  -                                       sum (abs (- match i))
  -                                     else sum ngram-limit))))
  -         (results (sort distances #'< :key #'cdr)))
  -    (let ((results
  -            (destructuring-bind ((best . score) . rest) results
  -              (cons (alpha-2 best)
  -                    (let ((base (* score cutoff)))
  -                      (loop for (lang . score) in rest
  -                            while (< score base)
  -                            collect (alpha-2 lang)))))))
  -      (values (first results) (rest results)))))
  +                               (language-score unknown model
  +                                               ngram-limit))))
  +         (sorted-distances (sort distances #'< :key #'cdr))
  +         (results
  +           (destructuring-bind ((best . score) . rest)
  +               sorted-distances
  +             (cons (alpha-2 best)
  +                   (let ((base (* score cutoff)))
  +                     (loop for (lang . score) in rest
  +                           while (< score base)
  +                           collect (alpha-2 lang)))))))
  +    (values (first results) (rest results))))
   
   (defun update-lm (lm input)
     (prog1 lm
  
positive.code:    input
     :remove-empty-subseqs t))
  
  (defun finalize-lm (lm &key (ngram-limit 400) remove-singletons)
    (when remove-singletons
      (maphash (lambda (k v)
                 (when (= v 1)
  
negative.code:   (defvar *base*
      #.(fad:pathname-directory-pathname (or *compile-file-truename* *load-truename*))))
  
  (defparameter *ngram-limit* 400
    "The default ngram limit for comparisons.")
  
  (defparameter *sample-size* 20000
    "The maximum sample size, in characters.")
  
  (defparameter *cutoff* 1.05
    "The default cutoff for plausible guesses.")
  
  (defun load-model (file)
    "Load a language model from FILE into a hash table."
    (let ((lines
            (lines
             (read-file-into-string
  