We'll look at a series of transformations, trying to improve your code:
Your code:
(let ((a 0) (lis '()))
(defun counter (len)
(setq lis (cons a lis)) (setq a (+ a 1))
(if (< a len) (counter len) lis)
))
Formatting
The typical formatting would look like:
(let ((a 0)
(list '()))
(defun counter (len)
(setq list (cons a list))
(setq a (+ a 1))
(if (< a len)
(counter len)
list)))
Problems
There are a bunch of problems with above code:
There is a LET
surrounding the function definition. This defines the variables a
and list
outside of the function? Why?
it looks very imperative
it's a recursive function which could run out of stack space
does not return the list of numbers in ascending order, which was a requirement in your problem statement
A better recursive version
Let's look at a recursive version:
(defun counter (len &optional (a 0) (list '()))
(if (>= a len)
list
(counter len
(1+ a)
(cons a list))))
Above gets rid of the surrounding LET
and makes the variables a
and list
optional parameters.
The base case is also the then
case in IF
.
It may be better to hide the variables a
and list
. So we can use a local recursive function:
(defun counter (len)
(labels ((counter-rec (a list)
(if (>= a len)
list
(counter-rec (1+ a) (cons a list)))))
(counter-rec 0 '())))
Above function uses a local function for the recursion.
We still may want to return the list in an ascending order:
(defun counter (len)
(labels ((counter-rec (a list)
(if (>= a len)
list
(counter-rec (1+ a) (cons a list)))))
(nreverse
(counter-rec 0 '()))))
Above uses the destructive function NREVERSE
, which we can use because the generated list is freshly created and not used anywhere else.
We can also count down in the recursive function. This gets rid of the NREVERSE
:
(defun counter (len)
(labels ((counter-rec (a list)
(if (minusp a)
list
(counter-rec (1- a) (cons a list)))))
(counter-rec (1- len) '())))
Using the DO loop for a non-recursive version
(defun counter (len)
(do* ((a len (1- a))
(list '() (cons a list)))
((zerop a) list)))
(defun counter (len)
(do* ((a len (1- a)) ; A from len to zero
(list '() (cons a list))) ; consing the result
((zerop a) list))) ; the end condition and
; list as the result
LOOP
Common Lisp provides a built-in powerful LOOP
macro, which let's us write:
(defun counter (len)
(loop for i below len
collect i))
Above iterates i
from 0
to (1- len)
and collects each i
into a list to return.
COUNTER is called IOTA in Lisp
A function like COUNTER
exist in Lisp libraries and is traditionally called iota
.
CL-USER 1 > (ql:quickload "alexandria")
("alexandria")
CL-USER 2 > (apropos "iota")
ALEXANDRIA:MAP-IOTA (defined)
ALEXANDRIA:IOTA (defined)
CL-USER 3 > (ALEXANDRIA:IOTA 10)
(0 1 2 3 4 5 6 7 8 9)