#-(and) "

P02 (*) Find the last but one box of a list.
    Example:
    * (my-but-last '(a b c d))
    (C D)
"

;; The nice, recursive solution:

(defun my-but-last (list)
  (cond
    ((endp list)                (error "Empty list"))
    ((endp (rest list))         (error "List too short"))
    ((endp (rest (rest list)))  list)
    (t                          (my-but-last (rest list)))))


;; The efficient, iterative solution:

(defun my-but-last (list)
  (cond
    ((endp list)                (error "Empty list"))
    ((endp (rest list))         (error "List too short"))
    (t  (loop
           :for result :on list
           :until (endp (rest (rest result)))
           :finally (return result)))))


;; The smartass, Common Lisp solution:

(defun my-but-last (list)
  (cond
    ((endp list)                (error "Empty list"))
    ((endp (rest list))         (error "List too short"))
    (t                          (last list 2))))




;; We test and signal an error when the list is too short (the
;; specifications asks for at least 2 boxes (cons cells).  We could
;; use CHECK-TYPE with a type specification corresponding to lists of
;; at least to cons cells:

(defun my-but-last (list)
  (check-type list (cons t (cons t t)))
  (if (endp (cddr list))
      list
      (my-but-last (cdr list))))


;; Now we may consider that checking the list at each recursive call
;; would be expensive (and useless).  Indeed, in general, checking the
;; arguments needs to be done only on public entry points (module
;; APIs), while internal functions can assume that their
;; preconditions, their arguments, and the global invariants of their
;; module are valid.  We may use LABELS to define internal recursive
;; functions (FLET if they're not recursive):

(defun my-but-last (list)
  (check-type list (cons t (cons t t)))
  (labels ((find-but-last (list)
             (if (endp (cddr list))
                 list
                 (find-but-last (cdr list)))))
    (find-but-last list)))

;; We are using endp to test and signal an error on dotted-lists such
;; as (a b c . d).  But the problem statement didn't strictly exclude
;; them.  One may argue that "list" covers dotted-lists, circular
;; lists as well as proper-lists.  Furthermore, the only constraint is
;; that there are two cons cells, nothing is said about the contents
;; of the CDR slot of the last cons cell.  Therefore we may use ATOM
;; to test for cdr of the last cons cell, or the negation, CONSP if we
;; exchange the branches of the IF:

(defun my-but-last (list)
  (check-type list (cons t (cons t t)))
  (labels ((find-but-last (list)
             (if (consp (cddr list))
                 (find-but-last (cdr list))
                 list)))
    (find-but-last list)))



;; Note: we would use check-type on functions close to the user,
;; typically, functions given user data, so that the user may be able
;; to substite a correct and meaningful value for a bad value.  But
;; functions in deeper code should probably directly signal an error
;; notably when they process massaged internal data that the user may
;; not recognize (and would be in pain to correct meaningfull).

;;;; THE END ;;;;
ViewGit