36

如何在 Common Lisp 中创建连续数字列表?

换句话说,rangeCommon Lisp 中 Python 的函数等价物是什么?

在 Python中range(2, 10, 2)返回[2, 4, 6, 8],第一个和最后一个参数是可选的。尽管 Emacs Lisp 有number-sequence.

范围可以使用循环宏来模拟,但我想知道生成具有起点和终点以及步骤的数字序列的公认方法。

相关:Scheme 中 Python 范围的模拟

4

10 回答 10

41

没有生成数字序列的内置方法,这样做的规范方法是执行以下操作之一:

  • 采用loop
  • 编写一个实用函数,使用loop

一个示例实现是(这只接受从“低”到“高”的计数):

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

这允许您指定(可选)最小值和(可选)步长值。

要生成奇数:(range 10 :min 1 :step 2)

于 2012-12-18T16:47:51.357 回答
19

alexandria 实现了 scheme 的 iota:

(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)
于 2012-12-27T11:35:39.747 回答
6

这是我解决问题的方法:

(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))

但这只是大体思路,还有很大的改进空间。


好的,因为这里似乎有讨论。我假设真正需要的是类似于 Python 的range生成器函数。从某种意义上说,它会生成一个数字列表,但它是通过每次迭代产生一个数字来实现的(这样它一次不会创建一个以上的项目)。生成器是一个有点少见的概念(很少有语言实现它),所以我假设提到 Python 表明需要这个确切的特性。

在对我上面的示例进行了一些批评之后,这里有一个不同的示例,它说明了为什么可以使用生成器而不是简单循环的原因。

(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

同样,这只是此功能用途的说明。使用它来生成整数可能很浪费,即使您需要分两步执行此操作,但生成器最好使用解析器,当您想要生成基于解析器先前状态构建的更复杂的对象时,例如,还有一堆其他的东西。好吧,您可以在这里阅读有关它的论点:http ://en.wikipedia.org/wiki/Generator_%28computer_programming%29

于 2012-12-18T17:52:30.757 回答
4

使用递归:

(defun range (min max &optional (step 1))
  (when (<= min max)
    (cons min (range (+ min step) max step))))
于 2014-06-15T11:32:15.330 回答
2

以简单的形式指定开始、停止、步骤:

(defun range (start stop step) 
  (do (
    (i start (+ i step)) 
    (acc '() (push i acc))) 
   ((>= i stop) (nreverse acc))))
于 2016-11-28T00:44:14.287 回答
1

你可能想试试

“用于 Common Lisp 的 Python 样式生成器。包括一个 itertools 端口。”

它在Quicklisp中可用。可能还有其他 Common Lisp 库可以提供帮助。

于 2018-05-05T07:17:33.090 回答
1

没有找到我想要的,也不想使用外部包,我最终编写了自己的版本,该版本与 python 版本不同(希望对其进行改进)并避免循环。如果您认为它确实效率低下并且可以改进它,请这样做。

;; 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"))))

注意:我也写了一个方案版本

于 2018-07-17T08:32:50.147 回答
0

这是一个生成数字列表的范围函数。我们使用do “循环”。如果有函数循环这样的东西,那么宏就是它。虽然没有递归,但当你构造一个do时,我发现思路非常相似。您考虑do中的每个变量的方式与考虑递归调用中的每个参数的方式相同。

我使用list*而不是conslist*与cons完全相同,但可以有 1、2 或更多参数。(list 1 2 3 4 nil)(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

这是一个测试会话:

CL-USER 23 >(范围 0 10)

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

CL-USER 24 >(范围 10 0 -1)

CL-USER 25 > (范围 10 0 1)

CL-USER 26 >(范围 1 21 2)

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

CL-USER 27 >(反向(范围 1 21 2))

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

CL-用户 28 >

此版本不适用于递减序列。但是,您会看到可以使用reverse来获得递减序列。

于 2018-11-08T05:09:08.813 回答
0

需要(range n)在一个刚刚拥有并可用的小型 Lisp中实现:dotimessetq

(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)))

例子:

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

> (range 10 15)
(10 11 12 13 14)
于 2019-09-05T23:33:31.153 回答
0

以防万一,这是 user1969453 的答案的类似物,它返回一个向量而不是一个列表:

(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)))

或者,如果您想预先分配向量并跳过“vector-push”习语:

(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))))
于 2020-04-08T14:38:43.967 回答