Common Lisp 中的高效收集函数

发布于 2024-10-08 11:01:52 字数 750 浏览 0 评论 0原文

我正在学习 Lisp,并编写了以下函数来收集结果列表。

(defun collect (func args num)
  (if (= 0 num)
      ()
      (cons (apply func args)
        (collect func args (- num 1)))))

它产生与内置循环函数类似的输出。

CL-USER> (collect #'random '(5) 10)
(4 0 3 0 1 4 2 1 0 0)
CL-USER> (loop repeat 10 collect (random 5))
(3 3 4 0 3 2 4 0 0 0)

然而,当我尝试生成一个 100,000 个元素长的列表时,我的收集函数会破坏堆栈,

CL-USER> (length (collect #'random '(5) 100000))
Control stack guard page temporarily disabled: proceed with caution

而循环版本则不会。

CL-USER> (length (loop repeat 100000 collect (random 5)))
100000

如何使我的版本更加节省空间,除了 consing 之外还有其他选择吗?我认为这是尾递归。我正在使用 sbcl。任何帮助都会很棒。

I'm learning Lisp and have written the following function to collect a list of results.

(defun collect (func args num)
  (if (= 0 num)
      ()
      (cons (apply func args)
        (collect func args (- num 1)))))

It produced similar output to the built in loop function.

CL-USER> (collect #'random '(5) 10)
(4 0 3 0 1 4 2 1 0 0)
CL-USER> (loop repeat 10 collect (random 5))
(3 3 4 0 3 2 4 0 0 0)

However my collect function blows the stack when I try to generate a list 100,000 elements long

CL-USER> (length (collect #'random '(5) 100000))
Control stack guard page temporarily disabled: proceed with caution

Whereas the loop version doesn't

CL-USER> (length (loop repeat 100000 collect (random 5)))
100000

How can I make my version more space efficient, are there alternatives to consing? I think it's tail recursive. I'm using sbcl. Any help would be great.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

别再吹冷风 2024-10-15 11:01:53

不,它不是尾递归。 ANSI Common Lisp 和你的代码都没有对此做出任何说明:

(defun collect (func args num)
  (if (= 0 num)
      ()
      (cons (apply func args)
            (collect func args (- num 1)))))

如果你查看你的代码,就会发现你对 COLLECT 的调用存在一个缺点。此 CONS 接收递归调用 COLLECT 的值。所以COLLECT不能是尾递归。通过引入累加器变量将函数重写为看起来尾递归的函数相对简单。各种 Lisp 或Scheme 文献应该对此进行描述。

在 Common Lisp 中,对迭代计算进行编程的默认方法是使用以下几种迭代结构之一:DO、DOTIMES、DOLIST、LOOP、MAP、MAPCAR...

Common Lisp 标准不提供尾调用优化 (TCO)。必须指定在存在其他几种语言功能的情况下 TCO 应该做什么。例如,动态绑定和特殊变量会对 TCO 产生影响。但 Common Lisp 标准根本没有提及一般性的 TCO 以及 TCO 可能产生的影响。 TCO 不是 ANSI Common Lisp 标准的一部分。

一些 Common Lisp 实现可以通过编译器开关启用各种尾部调用优化。请注意,启用这些功能的方法和限制都是特定于实现的。

总结:在 Common Lisp 中使用迭代结构而不是递归。

(defun collect (func args num)
  (loop repeat num
        collect (apply #'func args)))

额外的好处:它更容易阅读。

No, it is not tail recursive. Neither ANSI Common Lisp says anything about it nor your code:

(defun collect (func args num)
  (if (= 0 num)
      ()
      (cons (apply func args)
            (collect func args (- num 1)))))

If you look at your code, there is a CONS around your call to COLLECT. This CONS receives the value of the recursive call to COLLECT. So COLLECT can't be tail recursive. It's relatively simple to rewrite your function to something that looks tail recursive by introducing an accumulator variable. The various Lisp or Scheme literature should describe that.

In Common Lisp the default way to program an iterative computation is by using one of the several iterative constructs: DO, DOTIMES, DOLIST, LOOP, MAP, MAPCAR, ...

The Common Lisp standard does not provide tail call optimization (TCO). It would have to be specified what TCO should do in the presence of several other language features. For example dynamic binding and special variables have an effect on TCO. But the Common Lisp standard says simply nothing about TCO in general and about possible effects of TCO. TCO is not a part of the ANSI Common Lisp standard.

Several Common Lisp implementations have a way to enable various tail call optimizations with compiler switches. Note that both the way to enable those and the limitations are implementation specific.

Summary: In Common Lisp use iteration constructs and not recursion.

(defun collect (func args num)
  (loop repeat num
        collect (apply #'func args)))

Added bonus: it is easier to read.

春庭雪 2024-10-15 11:01:53

ANSI 标准不要求 Common Lisp 实现进行尾调用优化;然而,大多数值得他们关注的人(包括 SBCL)确实进行了优化。

另一方面,你的函数不是尾递归的。可以通过使用引入额外参数来累积中间结果的常见技巧将其变成一个:(

(defun collect (func args num)
  (labels ((frob (n acc)
             (if (= 0 n)
                 acc
                 (frob (1- n) (cons (apply func args) acc)))))
    (frob num nil)))

原始参数 FUNC 和 ARGS 在局部函数中被消除,因为它们相对于局部函数而言是恒定的)。

Common Lisp implementations are not required by the ANSI standard to do tail call optimization; however, most that worth their salt (including SBCL) do optimize.

Your function, on the other hand, is not tail recursive. It can be turned into one by using the common trick of introducing an extra parameter for accumulating the intermediate result:

(defun collect (func args num)
  (labels ((frob (n acc)
             (if (= 0 n)
                 acc
                 (frob (1- n) (cons (apply func args) acc)))))
    (frob num nil)))

(The original parameters FUNC and ARGS are eliminated in the local function since they are constant with recpect to it).

素食主义者 2024-10-15 11:01:53

那么,递归的替代方法是迭代。

您应该知道,Common Lisp 不需要其实现者进行尾递归,Scheme 不同。

(defun mycollect (func args num)
  (let ((accumulator '()))     ; it's a null list. 
    (loop for i from 1 to num 
      do
      ;;destructively cons up the accumulator with the new result + the old accumulator
      (setf accumulator                   
        (cons (apply func args) accumulator)))
  accumulator))                ; and return the accumulator

Well, the alternative to recursion is iteration.

You should know that Common Lisp does not require tail recursion from its implementors, unlike Scheme.

(defun mycollect (func args num)
  (let ((accumulator '()))     ; it's a null list. 
    (loop for i from 1 to num 
      do
      ;;destructively cons up the accumulator with the new result + the old accumulator
      (setf accumulator                   
        (cons (apply func args) accumulator)))
  accumulator))                ; and return the accumulator
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文