2

I just want to make a simple program in c-lisp; take in a number and return a list of all the numbers from 0 to that number. I'm relatively new to lisp and I'm curious how someone more experienced would do it. My program is this:

(let ((a 0) (lis '())) 
  (defun counter (len) 
    (setq lis (cons a lis)) (setq a (+ a 1)) 
    (if (< a len) (counter len) lis) 
    ))

But I feel like it doesn't quite catch the spirit of lisp, there gotta be more elegant ways right?

Another thought I had was some version of this, but how would you put the output into a list?

(defun counter2 (x) 
  (print x) 
  (cond ((< 0 x) (counter2 (- x 1)))))

Thank you for your time and eventual help, :)

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Or this one: [LISP - sequence of integers](https://stackoverflow.com/questions/41131196/lisp-sequence-of-integers) – Martin Půda Apr 22 '23 at 03:47
  • CLISP is not the name of the language, which is called Common Lisp or shorter CL. CLISP is a specific implementation of Common Lisp, largely implemented in C. – Rainer Joswig Apr 22 '23 at 06:33
  • What does it mean to put the output into a list? PRINT prints to a text stream. Do you want to collect the printed text? Why? – Rainer Joswig Apr 22 '23 at 06:57

3 Answers3

4

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)
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
1

Okay let's go through this:

(let ((a 0) (lis '())) 
  (defun counter (len) 
    (setq lis (cons a lis)) (setq a (+ a 1)) 
    (if (< a len) (counter len) lis) 
    ))

There is no reason why the let should be outside of the function definition. Put it inside:

  (defun counter (len)
    (let ((a 0) (lis '())) 
      (setq lis (cons a lis)) (setq a (+ a 1)) 
      (if (< a len) (counter len) lis) 
    ))

Now indent it properly (all closing parens are put at the end. You don't look at the parens - you just look at the indentation level. It will tell you how the levels depend on each other.

(defun counter (len)
  (let ((a 0) 
        (lis '())) 
    (setq lis (cons a lis)) 
    (setq a (+ a 1)) 
    (if (< a len) 
        (counter len) 
        lis)))

You can put the local variables defined in the let to the function's arguments as well. Because they are local variables for the function. Take them as optional arguments. This eliminates some parantheses.

(defun counter (len &optional (a 0) (lis '())) 
    (setq lis (cons a lis)) 
    (setq a (+ a 1)) 
    (if (< a len) 
        (counter len) 
        lis))

This prepares the function also for recursion. Because instead of setq-ing a and lis, you could call the function itself giving the new values for a and lis in a recursive way. The condition is actually the break condition. So what we do is to restructure this a little and use the recursive function call to eliminate the need for setq. By the way - you should rather use setf. setq is a special case of setf. Always use setf in common lisp.

(defun counter (len &optional (a 0) (lis '()))
    (if (not (< a len))     ;; break condition of the recursion
        lis
        (counter len (+ a 1) (cons a lis)))) ;; this eliminates `setq`s

Now comes something what many such recursive functions have in common. The breaking condition of the recursion if often a question for emptyness of alist (null l) or zero-ness of a number - in our case: (zerop len). You pass on len but increment a from round to round. You could achieve the same by asking for zero-ness of len and decrease len (decrement) from round to round. So len can keep all the info of the rounds - no a needed for counting the rounds!

(defun counter (len &optional (lis '()))
    (if (zerop len)     ;; typical break condition for recursion!
        lis
        (counter (- len 1) (cons len lis))))

I personally like to use cons because it shows next to each other condition and consequent:

(defun counter (n &optional (acc '()))
    (cond ((zerop n) (nreverse acc))
          (t (counter (1- n) (cons len acc)))))

Also you have to reverse the result. which I call acc for accumulator. This is by the way a typical idiom in recursive functions to collect/accumulate using cons and then to reverse (or for performance reasons to nreverse) the accumulator at the end.

Now this last form is the correct form and a very typical recursive function for such matter.

Increment and decrement by 1 is so common - for such kind of recursions, so that common lisp has 1- and 1+ for them.

Gwang-Jin Kim
  • 9,303
  • 17
  • 30
0

You can do it with DOTIMES

(defun counter (len)
  (let ((res ()))
    (dotimes (i (1+ len) (nreverse res))
      (push i res))))

or even easier with LOOP:

(defun counter (len)
  (loop for i from 0 to len collect i))
Barmar
  • 741,623
  • 53
  • 500
  • 612