【问题标题】:Lisp &rest parameters and recursive callsLisp &rest 参数和递归调用
【发布时间】:2018-06-14 15:16:32
【问题描述】:

我有以下 Common Lisp 函数:

(defun test(A &rest indexes)
  (if (null (first indexes))
      A
    (test (nth (+ 1 (first indexes)) A) (rest indexes))
  )
)

据我所知,&rest 参数在函数体中被视为列表,但由于

(rest indexes) 还返回一个列表,我一直使用嵌套列表作为参数。

例如(test '("a" "b" "c" ("d" "e")) 3 1 6 7)

在第二次调用时会导致索引为((1 6 7))

有什么方法可以通过我的列表而不会出现这个问题?

【问题讨论】:

    标签: function recursion lisp common-lisp variadic-functions


    【解决方案1】:

    基本样式规则:列表处理函数不要使用&rest 参数。

    为什么? Common Lisp 实现最多只能支持CALL-ARGUMENTS-LIMIT 数量的参数。此数字为 50 或更大,具体取决于实施。

    这意味着您的函数可能在某些实现过程中列出不超过五十个项目。

    更好:将列表作为单独的参数传递。

    (defun test (A indexes)
       ...)
    
    (test '("a" "b" "c" ("d" "e")) '(3 1 6 7))
    

    错误的解决方案:不要使用apply,因为它不能解决参数列表有限的问题。

    CLISP
    
    [1]> call-arguments-limit
    4096
    [2]> (defun l1 (&rest l) l)
    L1
    [3]> (apply #'l1 (loop repeat 5000 collect 1))
    
    *** - APPLY: too many arguments given to
          #<FUNCTION L1 (&REST L)
             (DECLARE (SYSTEM::IN-DEFUN L1))
             (BLOCK L1 L)>
    The following restarts are available:
    ABORT          :R1      Abort main loop
    

    【讨论】:

    • 我同意这个建议,你能说明如何正确编码函数吗?
    【解决方案2】:

    rest 是一个与first 配对的访问器函数,为您提供第一个元素和列表的其余部分。 restcdr 相同。

    &amp;rest 是一个 lambda list keyword,它会在其后面的变量名中删除剩余的参数。

    你真的在寻找apply。想象一下,我创建了一个可以接受 0 个或多个数字参数并将它们相加的函数:

    (defun add (&rest numbers)
      (apply #'+ numbers))
    

    Apply 可以接受两个以上的参数。第一个是要调用的函数,除了最后一个之外,都是放在最后一个参数元素前面的额外参数。您可以保证实现支持 50 个参数,或者在特定实现支持超过 50 个的特定实现中,函数可以接受的参数数量最多。

    (apply #'+ 1 2 '(3 4 5)) ; ==> 15
    

    现在通过&amp;restapply 进行递归会导致代码效率低下,因此您应该使用高阶函数、循环宏或创建助手:

    ;; higher order function
    (defun fetch-indexes (a &rest indexes)
      (mapcar (lambda (i) (nth i a)) indexes))
    
    ;; loop macro
    (defun fetch-indexes (a &rest indexes)
      (loop :for i :in indexes
            :collect (nth i a)))
    
    ;; helper function
    (defun fetch-indexes (a &rest indexes)
      (labels ((helper (indexes)
                 (if (endp indexes)
                     '()
                     (cons (nth (first indexes) a)
                           (helper (rest indexes))))))
        (helper indexes)))
    
    ;; test (works the same with all)
    (fetch-indexes '(a b c d) 2 3 0 1)
    ; ==> (c d a b)
    

    应避免在递归中使用 apply,但我将展示它是如何完成的。

    (defun fetch-indexes (a &rest indexes)
      (if (endp indexes)
          '()
          (cons (nth (first indexes) a)
                (apply #'fetch-indexes a (rest indexes)))))
    

    在您的示例中,您有嵌套列表。为了使它起作用,您还需要将其展平。我还没有这样做,所以这些支持一个适当的元素列表。

    【讨论】:

    • 为了使这个答案最佳,你应该展示如何在他的函数中使用apply
    • Apply can take any number of arguments -> 不在 Common Lisp 中。
    • @RainerJoswig 我实际上并没有想到限制,更多的是它支持超过 2 个。在这种情况下,您可以保证 apply 将与 (rest indexes) 一起使用,因为两者都需要支持50 且至少调用参数限制CALL-ARGUMENTS-LIMIT
    • '想象一下,我创建了一个可以接受任意数量的数字并将它们相加的函数:' - 您可能想更清楚一点,ADD 不能接受任意数量的数字。
    • @RainerJoswig OP 似乎想从结构中挑选一些元素,所以我怀疑它是否会接近 50 个参数保证的限制。第二次你通过了,我猜你会想要传递结构中的索引,也许做一次传递并索引数据,使其成为 O(n) 而不是 O(n^2),我现在所有的解决方案都是这样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 2016-08-09
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 2014-12-05
    相关资源
    最近更新 更多