置换闭包树的输出

时间:2011-01-28 23:16:29

标签: functional-programming lisp common-lisp

这是一个关于如何在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,我碰到它前面的那个,然后重置它后面的每一个闭包。

2 个答案:

答案 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,然后在下一次调用时再次从第一个值开始