This is really an extended comment on Rainer Joswig's beautiful answer.
What his minimum
function does is to essentially treat the first element of the list as a running best-guess-at-the-minimum, repeatedly building a new, shorter list with this best guess as its first element. That's really clever, because the whole algorithm for finding the minimum does work like that: guess the first number, compare it with the second and recurse appropriately.
An alternative approach to this sort of problem is to keep this running-best-guess as a distinct argument to the function which is going to compute the minimum. When used like this this alternative argument is sometimes called an 'accumulator' because in other related patterns it's used to accumulate results. This pattern only really works if you are willing to define an auxiliary function to do this work, since you generally don't want the top-level function to have this extra argument.
So, here is Rainer's function rewritten like this:
(defun minimum/tfb (list)
"function to return the minimum value of a list of numbers, via a recursive helper"
(when (null list)
;; I claim that the empty list is an error, so let's check this
;; and say so
(error "empty lists don't have minima"))
(labels ((minimum-loop (tail min-so-far)
;; TAIL is everything else we need to look at,
;; MIN-SO-FAR is the running minimum
(cond ((null tail)
;; we are done: the running minimum is the minimum
min-so-far)
((< min-so-far (first tail))
;; the running minimum is smaller than the first
;; element of TAIL so it is still our best bet:
;; recurse on the rest of TAIL
(minimum-loop (rest tail) min-so-far))
(t
;; the first element of TAIL is less than or
;; equal to min-so-far, so it is now our best bet
(minimum-loop (rest tail) (first tail))))))
;; Now start the process (remember we know LIST has at least one
;; element, which we use as out best guess).
(minimum-loop (rest list) (first list))))
It's pretty easy to see that this is just the same as Rainer's function (except it signals an error if the list it's given is empty, which is a matter of opinion), but it uses this local minimum-loop
function which does the work and has this extra argument.
It's possible to rewrite this in a slightly more gratuitously-functional way by observing that you can compute the extra argument at the point of call:
(defun minimum/tfb/gratuitous (list)
"function to return the minimum value of a list of numbers, via a recursive helper"
(when (null list)
;; I claim that the empty list is an error, so let's check this
;; and say so
(error "empty lists don't have minima"))
(labels ((minimum-loop (tail min-so-far)
;; TAIL is everything else we need to look at,
;; MIN-SO-FAR is the running minimum
(if (null tail)
;; we are done: the running minimum is the minimum
min-so-far
;; We have more to do: recurse, deciding which element
;; to use as the running minimum
(minimum-loop (rest tail)
(if (< min-so-far (first tail))
min-so-far (first tail))))))
;; Now start the process (remember we know LIST has at least one
;; element, which we use as out best guess).
(minimum-loop (rest list) (first list))))
I don't think this is actually stylistically any better and it might be worse. It's probably what I'd write however. (Python programmers hate my code.)
Finally note that both Rainer's function and my modifications of it are tail recursive. In a language like Scheme these functions would correspond to explicitly iterative processes. Indeed, in Scheme the iteration constructs are defined in terms of tail calls. Scheme also has a nice syntax for this sort of helper function pattern.
Here is a simple transliteration of my second function into a Scheme-family language (Racket):
(define (minimum lyst)
(when (null? lyst)
(error "empty lists do not have minima"))
(define (minimum-loop tail min-so-far)
(if (null? tail)
min-so-far
(minimum-loop (rest tail)
(if (< min-so-far (first tail))
min-so-far
(first tail)))))
(minimum-loop (rest lyst) (first lyst)))
(Note the annoying spelling of list
as lyst
because Scheme is a Lisp-1, and note that internal define
works like labels
in Scheme.)
And here is a conversion of that to use named-let
whose purpose is to support this sort of thing:
(define (minimum/loop lyst)
(when (null? lyst)
(error "empty lists do not have minima"))
(let minimum-loop ([tail (rest lyst)]
[min-so-far (first lyst)])
(if (null? tail)
min-so-far
(minimum-loop (rest tail)
(if (< min-so-far (first tail))
min-so-far
(first tail))))))