来自SICP的Clojure中的count-leaves

时间:2017-01-07 09:37:47

标签: clojure lisp sicp

我正在讨论SICP将问题转化为Clojure以学习Clojure并阅读SICP。目前,我仍然坚持第2.2.2节中的Count Leaves程序。

目标是编写一个采用树的列表表示的函数,例如: '(1 2'(3 4))并计算叶数,在本例中为4。

到目前为止,我最接近的是

(defn count-leaves
  [coll]
  (cond
    (nil? coll) 0
    (not (seq? coll)) 1
    :else (let [[left & right] coll] (+ (count-leaves left) (count-leaves right)))
    ))

但是,这不能正确处理子树。特别是,它评估

(count-leaves '('(1)))

到2而不是1。

注意Scheme实现from the book是:

(define (count-leaves x)
  (cond ((null? x) 0)
        ((not (pair? x)) 1)
        (else (+ (count-leaves (car x))
                 (count-leaves (cdr x))))))

2 个答案:

答案 0 :(得分:2)

将示例从一种语言翻译成另一种语言是一个很好的练习,但请记住,语言也有自己的习语和自己的核心库。

在Clojure中,使用clojure.walk行走数据结构非常简单。

在需要clojure.walk后,您可以运行postwalk-demo来查看数据结构的遍历方式:

(require '[clojure.walk :refer [postwalk postwalk-demo]])
(postwalk-demo '(1 2 (3 4)))
Walked: 1
Walked: 2
Walked: 3
Walked: 4
Walked: (3 4)
Walked: (1 2 (3 4))

然后,您可以设计一个函数来计算叶节点并将其传递给postwalk

(postwalk (fn [e]
            (if (seq? e) (apply + e) 1))
          '(1 2 (3 4)))

在postwalk遍历期间,叶节点被替换为1,seqs被其组成叶数的总和替换。

我意识到这是一个切实的答案,但也许你仍然觉得它很有用!

答案 1 :(得分:2)

<强>注释

正如@ jkiski的评论建议的那样,您的代码可以运行。所以没有问题。

但是我更喜欢先测试参数是否是一个序列。尝试一下(count-leaves '())评估0的方式!

切换cond的前两个条款,我们得到......

(defn count-leaves [coll]
  (cond
    (not (seq? coll)) 1
    (empty? coll) 0
    :else (+ (count-leaves (first coll)) (count-leaves (rest coll)))))

...我已经使用rest代替了解构中隐含的next,因此empty?代替nil?来测试它。这会正确处理您的代码所没有的nil值。但它仍然是正确的递归,因此仍然受到堆栈溢出的影响。

我更喜欢......

(defn count-leaves [coll]
  (if (seq? coll)
    (apply + (map count-leaves coll))
    1))

...仍然是递归的,但更清洁。

修改

我不得不收回我对@glts's solution的好评:postwalk是递归的,因此没有提供任何真正的优势。