如何在Common Lisp中创建连续数字的列表?
换句话说,python的 range
功能在COMON LISP中的等效是什么?
在Python range(2, 10, 2)
中返回[2, 4, 6, 8]
,第一个和最后一个参数是可选的。我找不到创建数字序列的惯用方法,尽管Emacs LISP具有number-sequence
。
可以使用循环宏来模拟范围,但我想知道生成具有起点和终点和步骤的数字序列的方法。
相关:方案中Python范围的模拟
没有内置的方式生成一系列数字,这样做的规范方法是做一个:
- 使用
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实现了方案的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
Generator函数的类似物。从某些意义上讲,这会生成数字列表,但是通过产生每个迭代的数字来做到这一点(这样一次,它不会创建一个比一个项目更多)。发电机是一个罕见的概念(很少有语言实施),因此我假设提及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_(computer_programpogramming)
使用递归:
(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))))
您可能想尝试蛇:
"普通LISP的Python样式生成器。包括Itertools的一个端口。
它可以在QuickLisp中使用。可能还有其他常见的LISP库可以提供帮助。
找不到我想要的东西,也不想使用外部软件包,我最终编写了自己的版本,这与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 " loop"。如果有功能循环之类的东西,那么 do 宏就是它。尽管没有递归,但是当您构造 do 时,我发现这种想法非常相似。您以递归调用中的每个参数为相同的方式来考虑 do 中的每个变量。
i使用 list*而不是 cons> cons 。列表*与 cons 完全相同,除非您可以有1、2或更多参数。(列表1 2 3 4 nil)和(CONS 1(CONS 2(CONS 3(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)
nil
cl-user 25>(范围10 0 1)
nil
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-user 28>
此版本不适用于减少序列。但是,您可以看到可以使用反向获得减少序列。
需要在刚有dotimes
和setq
的小LISP中实现(range n)
:
(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)))
或,如果您想预先分配矢量并跳过"向量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))))
递归解决方案:
(defun range(min max &optional (step 1))
(if (> min max)
()
(cons min (range (+ min step) max step))))
示例:
(range 1 10 3)
(1 4 7 10)