我试图理解Clojure中通过树或Clojure列表(或其他集合类型)表示的列表递归的惯用方法。
我可以编写以下内容来计算平面集合中的元素(忽略它不是尾递归的事实):
(defn length
([xs]
(if (nil? (seq xs))
0
(+ 1 (length (rest xs))))))
现在在Scheme或CL中,所有示例都只在列表上执行此操作,因此这些语言中的惯用基本案例测试将为(nil? xs)
。在Clojure中,我们希望这个函数适用于所有集合类型,惯用测试(nil? (seq xs))
,或者(empty? xs)
,还是完全不同的东西?
我要考虑的另一种情况是树遍历,即遍历表示树的列表或向量,例如, [1 2 [3 4]
。
例如,计算树中的节点:
(defn node-count [tree]
(cond (not (coll? tree)) 1
(nil? (seq tree)) 0
:else (+ (node-count (first tree)) (node-count (rest tree)))))
这里我们使用(not (coll? tree))
来检查原子,而在Scheme / CL中我们使用atom?
。我们还使用(nil? (seq tree))
来检查空集合。最后,我们使用first
和rest
将当前树解构为左分支和树的其余部分。
总而言之,Clojure中的以下形式是惯用的:
(nil? (seq xs))
来测试空集合(first xs)
和(rest xs)
深入了解馆藏(not (coll? xs))
检查原子答案 0 :(得分:10)
非空seqable的惯用测试是(seq coll)
:
(if (seq coll)
...
)
nil?
是不必要的,因为来自nil
的非seq
返回值保证为seq,因此nil
和false
都不是因此真的。
如果您想首先处理nil
案例,可以将if
更改为if-not
或seq
更改为empty?
;后者实现为seq
与not
的组合(这就是为什么写(not (empty? xs))
不是惯用的,参见empty?
的文档字符串。)
关于first
/ rest
- 记住rest
,next
的严格变体是有用的,使用它比包裹{{更惯用'更惯用1}}在rest
。
最后,seq
检查其参数是否为Clojure持久集合(coll?
的实例)。这是否是对“非原子”的适当检查取决于代码是否需要将Java数据结构作为非原子处理(通过互操作):例如clojure.lang.IPersistentCollection
与(coll? (java.util.HashSet.))
一样false
,但您可以在两者上调用(coll? (into-array []))
。新模块化contrib中的core.incubator
中有一个名为seq
的函数,它可以确定seqable?
对于给定的(seq x)
是否成功。
答案 1 :(得分:8)
我个人喜欢以下方法来递归集合:
(defn length
"Calculate the length of a collection or sequence"
([coll]
(if-let [[x & xs] (seq coll)]
(+ 1 (length xs))
0)))
特点:
请注意,一般情况下,如果可能的话,最好使用recur来编写递归函数,这样您就可以获得尾递归的好处,并且不会冒着炸毁堆栈的风险。因此,考虑到这一点,我实际上可能会编写如下特定函数:
(defn length
"Calculate the length of a collection or sequence"
([coll]
(length coll 0))
([coll accumulator]
(if-let [[x & xs] (seq coll)]
(recur xs (inc accumulator))
accumulator)))
(length (range 1000000))
=> 1000000