在Python中,您可以编写:
def firstn(n):
num = 0
while num < n:
yield num
num += 1
口齿不清是什么意思?
最佳答案
现有包装
使用GENERATORS
下载、安装并加载Quicklisp系统。然后,使用package:generators
(或者最好先定义自己的包)。
(ql:quickload :generators)
(use-package :generators)
为随机值定义无限生成器:
(defun dice (n)
(make-generator ()
;; repeatedly return a random value between 1 and N
(loop (yield (1+ (random n))))))
使用发电机:
(loop
with dice = (dice 6)
repeat 20
collect (next dice))
=> (1 2 6 1 1 4 4 2 4 3 6 2 1 5 6 5 1 5 1 2)
不过,请注意图书馆的作者所说的:
尽管据我所知,这个图书馆更像是一个有趣的玩具
确实有效。我想我从来没有在应用程序代码中使用过这个,
尽管我认为小心点,它可能是。
另见
ITERATE
包提供了一种定义generators以便在其迭代工具中使用的方法。SERIES
包提供类似流的数据结构和对它们的操作。Snakes库(据我所知,方法与
GENERATORS
相同)。关闭
在实践中,CL并不像Python所普及的那样依赖于生成器相反,当人们需要惰性序列时,他们依赖于闭包:
(defun dice (n)
(lambda ()
(1+ (random n))))
那么,等价于
next
的是对dice
生成的thunk的调用:(loop
with dice = (dice 6)
repeat 20
collect (funcall dice))
这是首选的方法,特别是因为不需要像使用生成器那样依赖分隔的连续性你的例子涉及一个状态,骰子例子不需要这个状态(有一个隐藏的状态影响
random
,但那是另一个故事)以下是计数器的典型实现方式:(defun first-n (n)
(let ((counter -1))
(lambda ()
(when (< counter n)
(incf counter)))))
高阶函数
或者,设计一个生成器,该生成器接受由生成器为每个值调用的回调函数可以使用任何funcallable,它允许调用方保留对代码执行的控制:
(defun repeatedly-throw-dice (n callback)
(loop (funcall callback (1+ (random n)))))
然后,您可以按如下方式使用它:
(prog ((counter 0) stack)
(repeatedly-throw-dice 6
(lambda (value)
(if (<= (incf counter) 20)
(push value stack)
(return (nreverse stack))))))
参见
PROG
的文档。成语
与构建函数不同,提供自定义值生成方式的数据源(如字符串中aregular expressions的匹配)也定期提供一个宏来抽象它们的控制流。使用方法如下:
(block 'outer
(let ((counter 0) stack)
(do-repeatedly-throw-dice (value 6)
;; For each iteration of the infinite loop,
;; VALUE is bound to a new random value.
(if (<= (incf counter) 20)
(push value stack)
(return-from 'outer (nreverse stack))))))
与上面的不同之处在于块是明确命名的这是因为
do-traversal
通常可以在它们的主体周围定义一个DO-X
块,这意味着任何封闭的NIL
块都会被隐藏。隐式
NIL
块允许您轻松地退出迭代: (let ((counter 0) stack)
(do-repeatedly-throw-dice (value 6)
(if (<= (incf counter) 20)
(push value stack)
(return (nreverse stack))))))
宏的一个可能实现是将主体包装成lambda形式,并使用上面定义的基于回调的版本:
(defmacro do-repeatedly-throw-dice ((var n) &body body)
`(block nil (repeatedly-throw-dice ,n (lambda (,var) ,@body))))
直接扩展成一个循环也是可能的:
(defmacro do-repeatedly-throw-dice ((var n) &body body)
(let ((max (gensym)) (label (make-symbol "NEXT")))
`(prog ((,max ,n) ,var)
,label
(setf ,var (1+ (random ,max)))
(progn ,@body)
(go ,label))))
上述形式的宏扩展步骤之一:
(BLOCK 'OUTER
(LET ((COUNTER 0) STACK)
(PROG ((#:G16053 6) VALUE)
#:NEXT (SETF VALUE (1+ (RANDOM #:G16053)))
(PROGN (IF (<= (INCF COUNTER) 20)
(PUSH VALUE STACK)
(RETURN-FROM 'OUTER (NREVERSE STACK))))
(GO #:NEXT))))
绑定
广义上讲,使用高阶函数或直接使用
NIL
宏构建生成器会得到相同的结果您可以实现一个与另一个(个人而言,我更喜欢先定义宏,然后使用宏定义函数,但是做相反的事情也很有趣,因为您可以重新定义函数,而不必重新编译宏的所有用法)。但是,仍然有一个区别:宏在迭代中重用相同的变量,而闭包每次都引入一个新的绑定。例如:
(let ((list))
(dotimes (i 10) (push (lambda () i) list))
(mapcar #'funcall list))
.... 返回:
(10 10 10 10 10 10 10 10 10 10)
普通Lisp中的大多数(如果不是全部)迭代器都倾向于这样工作,对于有经验的用户来说,这并不奇怪(事实上恰恰相反)如果通过重复调用闭包来实现
do-
,则结果将不同:(defmacro my-dotimes ((var count-form &optional result-form) &body body)
`(block nil
(alexandria:map-iota (lambda (,var) ,@body) ,count-form)
,result-form))
根据上述定义,我们可以看到:
(let ((list))
(my-dotimes (i 10) (push (lambda () i) list))
(mapcar #'funcall list))
... 返回:
(9 8 7 6 5 4 3 2 1 0)
为了得到与标准
dotimes
相同的结果,您只需要在构建闭包之前计算迭代变量:(let ((list))
(dotimes (i 10)
(let ((j i))
(push (lambda () j) list))))
如果您愿意,可以从宏中引入内部
dotimes
,但这很少实现。
关于lisp - 是否有一个与Python生成器相当的lisp等效项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44078331/