```#-(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