Mirzhan Irkegulov
Mirzhan Irkegulov

Reputation: 18075

Python's range() analog in Common Lisp

How to create a list of consecutive numbers in Common Lisp?

In other words, what is the equivalent of Python's range function in Common Lisp?

In Python range(2, 10, 2) returns [2, 4, 6, 8], with first and last arguments being optional. I couldn't find the idiomatic way to create a sequence of numbers, though Emacs Lisp has number-sequence.

Range could be emulated using loop macro, but i want to know the accepted way to generate a sequence of numbers with start and end points and step.

Related: Analog of Python's range in Scheme

Upvotes: 37

Views: 14961

Answers (13)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 9990

To let it behave exactly like Python's range (except it doesn't return a range object, but a list):

In [2]: list(range(3))
Out[2]: [0, 1, 2]

In [3]: list(range(1, 5))
Out[3]: [1, 2, 3, 4]

In [4]: list(range(1, 5, 2))
Out[4]: [1, 3]

In [5]: list(range(5, 1, -2))
Out[5]: [5, 3]

In [6]: list(range(0, -5, -1))
Out[6]: [0, -1, -2, -3, -4]

The solution with loop

(defun range (max &key (min 0) (step 1))
   (loop for n from min below max by step
      collect n))

fails when step is a negative number. But Python's range() has to deal with negativ step numbers.

That is, I would use a recursive definition %range and then use the outer function range to deal with the different number of arguments given:

(defun range (&rest args)
  "Behaves exactly like Python's range with the possible arguments:
   - stop             with default start 0 and default step 1
   - start stop       with default step 1
   - start stop step  whereby step can be a negative number."
  (labels ((%range (start stop step &optional (acc '()))
             (if (plusp step) 
                 (cond ((> step (- stop start)) 
                        (nreverse acc))
                       (t (%range (+ start step) stop step (cons start acc))))
                 (cond ((< step (- stop start))
                        (nreverse acc))
                       (t (%range (+ start step) stop step (cons start acc)))))))
    (let ((k (length args)))
      (cond ((= k 3) (apply #'%range args))
            ((= k 2) (funcall #'%range (first args) (second args) 1))
            ((= k 1) (funcall #'%range 0 (first args) 1))
            (t (error (format nil "Too many arguments given for range: ~A.~%" args)))))))

I used apply or funcall to call the inner recursive function %range defined in the labels section with the proper combination of given arguments and their default values.

And test by:

* (range 3)
(0 1 2)
* (range 1 5)
(1 2 3 4)
* (range 1 5 2)
(1 3)
* (range 5 1 -2)
(5 3)
* (range 0 -5)
NIL ;; which is () - an empty list
* (range 0 -5 -1)
(0 -1 -2 -3 -4)

This doesn't look nice but actually behaves more closely to Python's range(), except the result is not a range object - it returns fully executed lists.

Upvotes: 0

SomeGenericDev
SomeGenericDev

Reputation: 661

One possible approach would be using a while loop

(defun range (start end) (let ((i start)
                           (rangeList '()))
                       (loop while (<= i end)
                             do
                               (setq rangeList (append rangeList (list i)))
                               (setq i (+ 1 i)))
                       rangeList))

Upvotes: 0

burgerkalif
burgerkalif

Reputation: 39

Recursive solution:

(defun range(min max &optional (step 1))
  (if (> min max)
    ()
    (cons min (range (+ min step) max step))))

Example:

(range 1 10 3)
(1 4 7 10)

Upvotes: -2

David Atlas
David Atlas

Reputation: 51

Just in case, here is an analogue to user1969453's answer that returns a vector instead of a list:

(defun seq (from to &optional (step 1))
  (do ((acc (make-array 1 :adjustable t :fill-pointer 0))
       (i from (+ i step)))
      ((> i to) acc) (vector-push-extend i acc)))

Or, if you want to pre-allocate the vector and skip the 'vector-push' idiom:

(defun seq2 (from to &optional (step 1))
  (let ((size (+ 1 (floor (/ (- to from) step))))) ; size is 1 + floor((to - from) / step))
    (do ((acc (make-array size))
         (i from (+ i step))
         (count 0 (1+ count)))
        ((> i to) acc) (setf (aref acc count) i))))

Upvotes: 0

mythz
mythz

Reputation: 143369

Needed to implement (range n) in a tiny Lisp that just had dotimes and setq available:

(defun range (&rest args)
    (let ( (to '()) )
        (cond 
            ((= (length args) 1) (dotimes (i (car args))
                (push i to)))
            ((= (length args) 2) (dotimes (i (- (cadr args) (car args)))
                (push (+ i (car args)) to))))
    (nreverse to)))

Example:

> (range 10)
(0 1 2 3 4 5 6 7 8 9)

> (range 10 15)
(10 11 12 13 14)

Upvotes: 0

Indinfer
Indinfer

Reputation: 97

Here is a range function to generate a list of numbers. We use the do "loop". If there is such a thing as a functional loop, then do macro is it. Although there is no recursion, when you construct a do, I find the thinking is very similar. You consider each variable in the do in the same way you consider each argument in a recursive call.

I use list* instead of cons. list* is exactly the same as cons except you can have 1, 2, or more arguments. (list 1 2 3 4 nil) and (cons 1 (cons 2 (cons 3 (cons 4 nil)))).

(defun range (from-n to-n &optional (step 1)) ; step defaults to 1
  (do ((n from-n (+ n step))        ; n initializes to from-n, increments by step
       (lst nil (list* n lst)))     ; n "pushed" or "prepended" to lst

      ((> n to-n)                   ; the "recursion" termination condition
       (reverse lst))))             ; prepending with list* more efficient than using append
                                    ; however, need extra step to reverse lst so that
                                    ; numbers are in order

Here is a test session:

CL-USER 23 > (range 0 10)

(0 1 2 3 4 5 6 7 8 9 10)

CL-USER 24 > (range 10 0 -1)

NIL

CL-USER 25 > (range 10 0 1)

NIL

CL-USER 26 > (range 1 21 2)

(1 3 5 7 9 11 13 15 17 19 21)

CL-USER 27 > (reverse (range 1 21 2))

(21 19 17 15 13 11 9 7 5 3 1)

CL-USER 28 >

This version does not work for decreasing sequences. However, you see that you can use reverse to get a decreasing sequence.

Upvotes: 0

Kyuvi
Kyuvi

Reputation: 360

Not finding what I wanted nor wanting to use an external package, I ended up writing my own version which differs from the python version (hopefully improving on it) and avoids loop. If you think it is really inefficient and can improve on it, please do.

;; A version of range taking the form (range [[first] last [[step]]]).
;; It takes negative numbers and corrects STEP to the same direction
;; as FIRST to LAST then returns a list starting from FIRST and
;; ending before LAST
(defun range (&rest args)
  (case (length args)                                                      
    ( (0) '())                                                             
    ( (1) (range 0 (car args) (if (minusp (car args)) -1 1)))           
    ( (2) (range (car args) (cadr args)                                    
                 (if (>= (car args) (cadr args)) -1 1)))                   
    ( (3) (let* ((start (car args)) (end (cadr args))                      
                 (step (abs (caddr args))))
           (if (>=  end start)
             (do ((i start (+ i step))
                  (acc '() (push i acc)))
               ((>= i end) (nreverse acc)))
             (do ((i start (- i step))
                  (acc '() (push i acc)))
               ((<= i end) (nreverse acc))))))
    (t (error "ERROR, too many arguments for range"))))


;; (range-inc [[first] last [[step]]] ) includes LAST in the returned range
(defun range-inc (&rest args)
  (case (length args)
    ( (0) '())
    ( (1) (append (range (car args)) args))
    ( (2) (append (range (car args) (cadr args)) (cdr args)))
    ( (3) (append (range (car args) (cadr args) (caddr args))
          (list (cadr args))))
    (t (error "ERROR, too many arguments for range-inc"))))

Note: I wrote a scheme version as well

Upvotes: 1

gsl
gsl

Reputation: 1163

You may want to try snakes:

"Python style generators for Common Lisp. Includes a port of itertools."

It is available in Quicklisp. There may be other Common Lisp libraries that can help.

Upvotes: 1

user1969453
user1969453

Reputation:

In simple form specifying start, stop, step:

(defun range (start stop step) 
  (do (
    (i start (+ i step)) 
    (acc '() (push i acc))) 
   ((>= i stop) (nreverse acc))))

Upvotes: 3

Mark Karpov
Mark Karpov

Reputation: 7599

Using recursion:

(defun range (min max &optional (step 1))
  (when (<= min max)
    (cons min (range (+ min step) max step))))

Upvotes: 6

Vatine
Vatine

Reputation: 21288

There is no built-in way of generating a sequence of numbers, the canonical way of doing so is to do one of:

  • Use loop
  • Write a utility function that uses loop

An example implementation would be (this only accepts counting "from low" to "high"):

(defun range (max &key (min 0) (step 1))
   (loop for n from min below max by step
      collect n))

This allows you to specify an (optional) minimum value and an (optional) step value.

To generate odd numbers: (range 10 :min 1 :step 2)

Upvotes: 43

Pavel Penev
Pavel Penev

Reputation: 679

alexandria implements scheme's iota:

(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)

Upvotes: 21

user797257
user797257

Reputation:

Here's how I'd approach the problem:

(defun generate (from to &optional (by 1))
  #'(lambda (f)
      (when (< from to)
        (prog1 (or (funcall f from) t)
          (incf from by)))))

(defmacro with-generator ((var from to &optional (by 1)) &body body)
  (let ((generator (gensym)))
    `(loop with ,generator = (generate ,from ,to ,by)
        while
          (funcall ,generator
                   #'(lambda (,var) ,@body)))))

(with-generator (i 1 10)
    (format t "~&i = ~s" i))

But this is just the general idea, there's a lot of room for improvement.


OK, since there seems to be a discussion here. I've assumed that what is really needed is the analogue to Python's range generator function. Which, in certain sense generates a list of numbers, but does it so by yielding a number each iteration (so that it doesn't create more then one item at a time). Generators are a somewhat rare concept (few languages implement it), so I assumed that the mention of Python suggested that this exact feature is desired.

Following some criticism of my example above, here's a different example that illustrates the reason to why a generator might be used rather then a simple loop.

(defun generate (from to &optional (by 1))
  #'(lambda ()
      (when (< from to)
        (prog1 from
          (incf from by)))))

(defmacro with-generator
    ((var generator &optional (exit-condition t)) &body body)
  (let ((g (gensym)))
    `(do ((,g ,generator))
         (nil)
       (let ((,var (funcall ,g)))
         (when (or (null ,var) ,exit-condition)
           (return ,g))
         ,@body))))

(let ((gen
       (with-generator (i (generate 1 10) (> i 4))
         (format t "~&i = ~s" i))))
  (format t "~&in the middle")
  (with-generator (j gen (> j 7))
    (format t "~&j = ~s" j)))

;; i = 1
;; i = 2
;; i = 3
;; i = 4
;; in the middle
;; j = 6
;; j = 7

This is, again, only an illustration of the purpose of this function. It is probably wasteful to use it for generating integers, even if you need to do that in two steps, but generators are best with parsers, when you want to yield a more complex object which is built based upon the previous state of the parser, for example, and a bunch of other things. Well, you can read an argument about it here: http://en.wikipedia.org/wiki/Generator_%28computer_programming%29

Upvotes: 6

Related Questions