我最近发现了提供数据结构导航和转换功能的Specter库,并用Clojure编写。
实施一些API作为学习练习似乎是一个好主意。 Spectre实现了一个API,它将一个函数和一个嵌套结构作为参数,并从嵌套结构中返回一个元素向量,满足下面的函数:
(select (walker number?) [1 :a {:b 2}])
=> [1 2]
以下是我尝试使用类似API实现的功能:
(defn select-walker [afn ds]
(vec (if (and (coll? ds) (not-empty ds))
(concat (select-walker afn (first ds))
(select-walker afn (rest ds)))
(if (afn ds) [ds]))))
(select-walker number? [1 :a {:b 2}])
=> [1 2]
我尝试使用list comprehension,looping以及使用cons和conj来实施select-walker
。在所有这些情况下
返回值是嵌套列表,而不是元素的平面向量。
然而,我的实现似乎不是惯用的Clojure,并且时间和空间复杂性很差。
(time (dotimes [_ 1000] (select (walker number?) (range 100))))
"Elapsed time: 19.445396 msecs"
(time (dotimes [_ 1000] (select-walker number? (range 100))))
"Elapsed time: 237.000334 msecs"
请注意,我的实现速度比Spectre的实现慢约12倍。
我对select-walker
的实施有三个问题。
select-walker
可能的尾递归实现吗?select-walker
吗?select-walker
执行得更快的任何提示?答案 0 :(得分:2)
至少有两种可能使尾部递归。第一个是循环处理数据,如下所示:
(defn select-walker-rec [afn ds]
(loop [res [] ds ds]
(cond (empty? ds) res
(coll? (first ds)) (recur res
(doall (concat (first ds)
(rest ds))))
(afn (first ds)) (recur (conj res (first ds)) (rest ds))
:else (recur res (rest ds)))))
在repl中:
user> (select-walker-rec number? [1 :a {:b 2}])
[1 2]
user> user> (time (dotimes [_ 1000] (select-walker-rec number? (range 100))))
"Elapsed time: 19.428887 msecs"
(简单的选择步行者为我工作约200ms)
第二个(虽然更慢,更适合更困难的任务)是使用zippers
:
(require '[clojure.zip :as z])
(defn select-walker-z [afn ds]
(loop [res [] curr (z/zipper coll? seq nil ds)]
(cond (z/end? curr) res
(z/branch? curr) (recur res (z/next curr))
(afn (z/node curr)) (recur (conj res (z/node curr))
(z/next curr))
:else (recur res (z/next curr)))))
user> (time (dotimes [_ 1000] (select-walker-z number? (range 100))))
"Elapsed time: 219.015153 msecs"
这个非常慢,因为拉链在更复杂的结构上运行。它的强大功能为这项简单的任务带来了不必要的开销。
我猜的最常用的方法是使用tree-seq
:
(defn select-walker-t [afn ds]
(filter #(and (not (coll? %)) (afn %))
(tree-seq coll? seq ds)))
user> (time (dotimes [_ 1000] (select-walker-t number? (range 100))))
"Elapsed time: 1.320209 msecs"
它非常快,因为它会产生一系列懒惰的结果。事实上,你应该实现其公平测试的数据:
user> (time (dotimes [_ 1000] (doall (select-walker-t number? (range 100)))))
"Elapsed time: 53.641014 msecs"
关于这个变体还有一点需要注意的是,它不是尾递归的,所以在真正深度嵌套的结构的情况下它会失败(也许我错了,但我想这是几千级嵌套),仍然适合大多数情况。