如何通过列表修复递归搜索

时间:2019-04-04 22:09:32

标签: clojure

我目前正在尝试学习Clojure。但是我在创建一个递归搜索列表中每个元素并返回列表中“ a”的数量的函数时遇到了麻烦。

我已经想出了如何迭代地执行它,但是我在递归地执行它时遇到了麻烦。我尝试将“ seq”更改为“ empty?”但这也不起作用。


(defn recursive-a [& lst]
 (if (seq lst)
     (if (= (first lst) "a")
         (+ 1 (recursive-a (pop (lst))))
         (+ 0 (recursive-a (pop (lst)))))
     0))

3 个答案:

答案 0 :(得分:1)

欢迎使用堆栈溢出社区。

您的代码很好,只是您犯了一些小错误。

首先,在lst参数周围还有一对括号,您可以将它们转发到递归函数。在LISP语言中,花括号表示功能评估。因此,首先您应该删除那些。

第二件事是&参数语法糖。在确定它如何影响代码之前,您不希望使用它。

进行这些更改后,代码如下:

  (defn recursive-a [lst]
 (if (seq lst)
     (if (= (first lst) "a")
         (+ 1 (recursive-a (pop lst)))
         (+ 0 (recursive-a (pop lst))))
     0))


(recursive-a (list "a" "b" "c"))

您可以在网络环境中运行它:https://repl.it/languages/clojure

答案 1 :(得分:0)

欢迎堆栈溢出。

通过显式调用recursive-a,原始实现在每次递归时都会消耗堆栈。如果提供了足够大的列表作为输入,则此功能最终将耗尽堆栈并崩溃。有几种方法可以解决此问题。

用于处理此类情况的经典Lisp-y方法之一是提供该函数的第二个实现,该函数将运行计数作为输入参数传递给“内部”函数:

(defn recursive-a-inner [cnt lst]
  (cond
    (seq lst) (cond
                (= (first lst) "a") (recur (inc cnt) (rest lst))
                :else               (recur cnt (rest lst)))
    :else cnt))

(defn recursive-a [& lst]
  (recursive-a-inner 0 lst))

通过执行此操作,“内部”版本允许将递归推到尾部位置,以便可以使用Clojure的recur关键字。它的实现不像原始的那么干净,但是它的优点是不会炸毁堆栈。

处理此问题的另一种方法是使用Clojure的loop-ing,它允许在函数体内进行递归。结果与上面的“内部”功能基本相同:

(defn recursive-a [& lp]
  (loop [cnt  0
         lst  lp]
    (cond
      (seq lst) (cond
                  (= (first lst) "a") (recur (inc cnt) (rest lst))
                  :else               (recur cnt (rest lst)))
      :else cnt)))

如果我们放弃了显式递归的要求,我们可以使其变得更简单:

(defn not-recursive-a [& lst]
  (apply + (map #(if (= % "a") 1 0) lst)))

好运。

答案 2 :(得分:0)

本着学习的精神:

您是否可以使用&。两者都很好。区别在于您随后将如何调用函数,并且您必须记住在重复执行时使用apply

此外,只需使用firstrest。它们都很安全,可以在nil和空列表上使用,分别返回nil和空列表:

(first [])  ;; -> nil
(first nil) ;; -> nil
(rest [])   ;; -> ()
(rest nil)  ;; -> ()

这就是我如何重新设计您的想法:

;; With '&'
(defn count-a [& lst]
  (if-let [a (first lst)]
    (+ (if (= a "a") 1 0)
       (apply count-a (rest lst)))  ;; use 'apply' here
    0))
;; call with variable args, *not* a list
(count-a "a" "b" "a" "c") 

;; Without '&'
(defn count-a [lst]
  (if-let [a (first lst)]
    (+ (if (= a "a") 1 0)
       (count-a (rest lst)))
    0))
;; call with a single arg: a vector (could be a list or other )
(count-a ["a" "b" "a" "c"])  

但是,这些方法并不安全,因为它们不使用尾递归,因此,如果您的列表很大,那么您将被淘汰!

因此,我们使用recur。但是,如果您不想定义其他“帮助器”功能,则可以使用loop作为“重复”目标:

;; With '&'
(defn count-a [& lst]
  (loop [c 0  lst lst] ;; 'recur' will loop back to this point
    (if-let [a (first lst)]
      (recur (if (= a "a") (inc c) c) (rest lst))
      c)))
(count-a "a" "b" "a" "c")

;; Without '&'
(defn count-a [lst]
  (loop [c 0  lst lst]
    (if-let [a (first lst)]
      (recur (if (= a "a") (inc c) c) (rest lst))
      c)))
(count-a ["a" "b" "a" "c"])

话虽如此,这也是我也会使用的:

;; With '&'
(defn count-a [& lst]
  (count (filter #(= % "a") lst)))
(count-a "a" "b" "a" "c")

;; Without '&'
(defn count-a [lst]
  (count (filter #(= % "a") lst)))
(count-a ["a" "b" "a" "c"])