这是一个关于如何在Lisp中实现以下内容的概念性问题(在我的情况下假设Common Lisp,但任何方言都可以)。假设你有一个函数创建闭包,它按顺序迭代任意集合(或以其他方式返回不同的值)数据,并在耗尽时返回nil,即
(defun make-counter (up-to)
(let ((cnt 0))
(lambda ()
(if (< cnt up-to)
(incf cnt)
nil))))
CL-USER> (defvar gen (make-counter 3))
GEN
CL-USER> (funcall gen)
1
CL-USER> (funcall gen)
2
CL-USER> (funcall gen)
3
CL-USER> (funcall gen)
NIL
CL-USER> (funcall gen)
NIL
现在,假设您正在尝试置换其中一个或多个闭包的组合。 如何实现一个返回一个新闭包的函数,该闭包随后会创建一个包含在其中的所有闭包的排列?即:
(defun permute-closures (counters)
......)
以下情况属实:
CL-USER> (defvar collection (permute-closures (list
(make-counter 3)
(make-counter 3))))
CL-USER> (funcall collection)
(1 1)
CL-USER> (funcall collection)
(1 2)
CL-USER> (funcall collection)
(1 3)
CL-USER> (funcall collection)
(2 1)
...
等等。
我最初设计它的方法是在初始计数lambda中添加一个'pause'参数,这样当迭代时你仍然可以调用它并接收旧的缓存值,如果传递“:pause t”,希望制作排列稍微清洁一点。此外,虽然上面的示例是两个相同闭包的简单列表,但列表可以是任意复杂的树(可以以深度优先的顺序进行置换,并且得到的置换集将具有树的形状。)。
我实现了这个,但我的解决方案不是很干净,我正在尝试调查其他人如何解决这个问题。
提前致谢。
编辑感谢您的所有答案。我最终做的是向生成器添加一个'continue'参数,并通过用置换该列表的闭包替换任何嵌套列表来展平我的结构。除非传递“继续”,否则生成器不会前进并始终返回最后一个缓存的值。然后我只是递归调用每个发生器,直到我到达最后一个cdr或nil。如果我到了最后一个cdr,我就碰到了它。如果我得到一个NIL,我碰到它前面的那个,然后重置它后面的每一个闭包。
答案 0 :(得分:2)
您显然需要某种方法来使用生成器返回的每个值多次。
除了Rainer Joswig的建议外,还会想到三种方法。
permute-closures
可以通过将每个生成器存储在列表中来记住每个生成器返回的值,并重复使用它。这种方法显然意味着一些内存开销,如果生成的序列可以是无限的,它将无法正常工作。
在这种方法中,您将更改permute-closures
的签名,将其作为参数而不是现成的生成器,而是创建它们的thunks。您的示例将如下所示:
(permute-closures (list (lambda () (make-counter 3))
(lambda () (make-counter 3))))
这样,permute-closures
能够通过简单地重新创建它来重置生成器。
您可以提供一种制作发电机及其状态副本的方法。这有点像方法#2,因为permute-closures
会根据需要重置生成器,除非重置将通过恢复到原始状态的副本来完成。此外,您可以进行部分重置(即,回溯到任意点而不仅仅是开头),这可能会或可能不会使permute-closures
的代码变得更加简单。
在具有第一类延续的语言(如Scheme)中复制生成器状态可能会稍微容易一些,但如果所有生成器都遵循某些预定义的结构,则使用define-generator
宏或某些宏来抽象它应该是可能的在Common Lisp中也是如此。
答案 1 :(得分:1)
我会将其中一个添加到计数器中:
能够将计数器重置为开头
让计数器在完成计数后返回NIL,然后在下一次调用时再次从第一个值开始