Clojure:只能从尾部位置重复出现

时间:2012-06-02 16:47:09

标签: recursion clojure

我正在尝试以递归方式反转列表,但在运行时会收到Can only recur from tail position。这究竟是什么意思,我的代码如何改进以便它起作用?

(defn recursive-reverse [coll]
  (loop [coll coll]
    (if (< (count coll) 2) '(coll)
      (conj (first coll) (recur (rest coll)))
      )))

修改

奥斯卡解决方案的输出。它适用于列表但不适用于矢量吗?

user=> (= (recursive-reverse [1 2 3 4 5]) (recursive-reverse '(1 2 3 4 5)))
false
user=> (= '(1 2 3 4 5) [1 2 3 4 5])
true
user=> (recursive-reverse [1 2 3 4 5])
[1 2 3 4 5]
user=> (recursive-reverse '(1 2 3 4 5))
(5 4 3 2 1)

3 个答案:

答案 0 :(得分:21)

错误Can only recur from tail position意味着您没有将recur作为函数递归部分中的最后一个表达式调用 - 事实上,在您的代码中conj是最后一个表达式。

使您的代码有效的一些改进:

  • 询问集合是否为空基础情况,而不是比较其长度是否小于2
  • conj收到第一个参数的集合,而不是元素
  • 最好使用cons而不是conj(根据documentation,根据集合的具体类型在不同位置添加新元素)。这样,如果输入集合是列表或向量,则返回的集合将被反转(尽管返回集合的类型始终为clojure.lang.Cons,无论输入集合的类型如何)
  • 请注意,'(coll)是一个包含单个元素的列表(符号coll),而不是实际的集合
  • 要正确反转列表,您需要遍历输入列表并将每个元素附加到输出列表的开头;使用累加器参数
  • 在函数的尾部位置利用尾递归调用recur;通过这种方式,每个递归调用都会占用一定量的空间,并且堆栈不会无限增长

我相信这是你的目标:

(defn recursive-reverse [coll]
  (loop [coll coll
         acc  (empty coll)]
        (if (empty? coll)
            acc
            (recur (rest coll) (cons (first coll) acc)))))

答案 1 :(得分:7)

你只能在Clojure中从尾部位置调用recur。它是语言设计的一部分,是与JVM相关的限制。

您可以在不使用recur的情况下调用函数名称(使用递归),并且根据您的程序结构,例如是否使用惰性序列,您可能没有堆栈爆炸。但是你最好使用recur,并且使用带有recur的循环允许你进行一些本地绑定。

以下是来自4Clojure.com的示例,其中使用递归而不重复。

(fn flt [coll]
  (let [l (first coll) r (next coll)]
    (concat 
      (if (sequential? l)
        (flt l)
        [l])
      (when (sequential? r)
        (flt r)))))

答案 2 :(得分:2)

使代码工作的最简单方法是使用函数名而不是recur

(defn recursive-reverse [coll]
  (loop [coll coll]
    (if (< (count coll) 2) '(coll)
      (conj (first coll) (recursive-reverse (rest coll)))
      )))

当然,它会使调用堆栈变得庞大并且错误输出足够大的输入。

这是另一种编写尾随调用友好版本的递归反转的方法:

(defn recursive-reverse [s]
  (letfn [
    (my-reverse [s c]
      (if (empty? s)
        c
        (recur (rest s) (conj c (first s)))))]
    (my-reverse s '())))

它将项目从输入列表的头部拉出,并将它们添加到累加器列表的头部。

'尾部位置'只意味着recur是函数在返回之前完成的最后一件事。这与你的“自然递归”解决方案不同,在解决方案中,函数在调用自身和返回之间仍有工作。