【问题标题】:An efficient collect function in Common LispCommon Lisp 中的一个高效收集函数
【发布时间】:2011-05-27 17:31:17
【问题描述】:

我正在学习 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 个元素长的列表时,我的 collect 函数会破坏堆栈

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。任何帮助都会很棒。

【问题讨论】:

    标签: optimization recursion lisp common-lisp


    【解决方案1】:

    不,它不是尾递归。 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)))
    

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

    【讨论】:

      【解决方案2】:

      ANSI 标准不要求通用 Lisp 实现进行尾调用优化;但是,大多数值得他们关注的东西(包括 SBCL)都会进行优化。

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

      (defun collect (func args num) (标签 ((frob (n acc) (如果(= 0 n) ACC (frob (1- n) (cons (apply func args) acc))))) (从零开始)))

      (原始参数 FUNC 和 ARGS 在局部函数中被消除,因为它们是常数)。

      【讨论】:

        【解决方案3】:

        嗯,递归的替代方法是迭代。

        您应该知道 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-08-06
          • 1970-01-01
          • 2017-10-19
          • 2015-01-17
          • 2020-08-16
          • 1970-01-01
          • 2011-04-11
          • 1970-01-01
          相关资源
          最近更新 更多