如何在 Common Lisp 中创建连续数字列表?
换句话说,range
Common Lisp 中 Python 的函数等价物是什么?
在 Python中range(2, 10, 2)
返回[2, 4, 6, 8]
,第一个和最后一个参数是可选的。尽管 Emacs Lisp 有number-sequence
.
范围可以使用循环宏来模拟,但我想知道生成具有起点和终点以及步骤的数字序列的公认方法。
如何在 Common Lisp 中创建连续数字列表?
换句话说,range
Common Lisp 中 Python 的函数等价物是什么?
在 Python中range(2, 10, 2)
返回[2, 4, 6, 8]
,第一个和最后一个参数是可选的。尽管 Emacs Lisp 有number-sequence
.
范围可以使用循环宏来模拟,但我想知道生成具有起点和终点以及步骤的数字序列的公认方法。
没有生成数字序列的内置方法,这样做的规范方法是执行以下操作之一:
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)
alexandria 实现了 scheme 的 iota:
(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)
这是我解决问题的方法:
(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
使用递归:
(defun range (min max &optional (step 1))
(when (<= min max)
(cons min (range (+ min step) max step))))
以简单的形式指定开始、停止、步骤:
(defun range (start stop step)
(do (
(i start (+ i step))
(acc '() (push i acc)))
((>= i stop) (nreverse acc))))
没有找到我想要的,也不想使用外部包,我最终编写了自己的版本,该版本与 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"))))
注意:我也写了一个方案版本
这是一个生成数字列表的范围函数。我们使用do “循环”。如果有函数循环这样的东西,那么做宏就是它。虽然没有递归,但当你构造一个do时,我发现思路非常相似。您考虑do中的每个变量的方式与考虑递归调用中的每个参数的方式相同。
我使用list*而不是cons。list*与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来获得递减序列。
需要(range n)
在一个刚刚拥有并可用的小型 Lisp中实现:dotimes
setq
(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)
以防万一,这是 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))))