Clojure 序列库的惰性状态处理
假设我想处理一个序列,其中的处理是有状态的,我想使用Clojure序列库以一种惰性的方式来完成 作为一个具体的例子,假设我想要实现Clojure 序列库的惰性状态处理,clojure,Clojure,假设我想处理一个序列,其中的处理是有状态的,我想使用Clojure序列库以一种惰性的方式来完成 作为一个具体的例子,假设我想要实现distinct,它自然地被实现为一个有状态过滤器,跟踪所看到的元素。我的第一个尝试是不使用序列库,而是lazy seq: (defn distinct' [coll] (let [process (fn process [coll seen] (lazy-seq (when-let [[
distinct
,它自然地被实现为一个有状态过滤器,跟踪所看到的元素。我的第一个尝试是不使用序列库,而是lazy seq
:
(defn distinct' [coll]
(let [process (fn process [coll seen]
(lazy-seq
(when-let [[x & r] (seq coll)]
(if (contains? seen x)
(process r seen)
(cons x (process r (conj seen x)))))))]
(process coll #{})))
我同意上面的说法,但如果可以的话,我更愿意使用map
和filter
等方法。我一直在为如何干净利落地做这件事而挣扎。一种有效的方法是利用减少量
:
(defn distinct'' [coll]
(->> (reductions (fn [[_ _ seen] x]
(if (contains? seen x)
[false nil seen]
[true x (conj seen x)]))
[false nil #{}]
coll)
(filter first)
(map second)))
基本上,distinct'
实现了使用更高级别抽象的目标(map
,filter
,reduces
)。但是它不能过于复杂,特别是我让它作为累加器传递的向量
如果我试图直接将状态嵌入到一个谓词中,以便与过滤器
一起使用,虽然它更接近我的想象,但它似乎是“错误的”,我甚至不好意思编写以下代码(过滤器
的文档甚至说谓词应该没有副作用):
我的问题:
有没有一种方法可以像这样用序列库以干净的方式进行惰性状态处理(即使对于这个
distinct
示例)?或者,lazy seq
通常是最干净的方法吗?我喜欢distinct'
,尽管我会内联进程
助手函数,比如((fn进程…)coll{})
。使用lazy-seq和递归来解决问题没有什么错,试图通过将所有内容都转换为map
/过滤器来避免它们,可能会导致程序的可读性大大降低
如果你不介意把车停在平地/有用的地方,你可以通过以下方式让它更漂亮:
它可以扩展为与您的不同的'
等价的东西,这似乎是一个意见问题。我认为lazy-seq方法更清晰。我的0.02$:我不认为最后一个方法是错误的,我不理解内部状态的变化是副作用,它是一个非常可读的实现,但我同意这是一个意见问题,你应该避免使用[x&xs]
在从另一个惰性序列生成惰性序列时进行解构,因为它会强制输入序列中的两个元素,以确定xs
是否应为零,如果xs
的生成成本很高,这可能是不好的。对于库质量代码,您必须先使用和rest
。这一点很好!顺便说一句:我对一个概念很感兴趣,即拥有一个最简单的正确性推理的“参考实现”,以及一个经过优化的“生产”或“库”实现,可能与通过测试的参考impl相比。检查。我发现distinct
的发布实现非常吸引人,它的内部循环/recur
可以避免在寻找新元素时出现惰性seq
。
(defn distinct''' [coll]
(let [seen (atom #{})]
(filter (fn [x]
(if (contains? @seen x)
false
(do (swap! seen conj x)
true)))
coll)))
(defn distinct'''' [coll]
(lazy-loop [coll coll, seen #{}]
(when-let [[x & r] (seq coll)]
(if (contains? seen x)
(recur r seen)
(cons x (lazy-recur r (conj seen x)))))))