让我们说我想要处理一个序列,其中处理是有状态的,我想使用Clojure序列库以懒惰的方式执行此操作。
作为一个具体的例子,假设我想实现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
之类的方法。我正在努力干净地完成这项工作。一种有效的方法是使用reductions
:
(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
,reductions
)同时保持懒惰的目标。但它没有过于复杂,尤其是我将它作为累加器传递的向量。
如果我尝试将状态直接嵌入谓词中以与filter
一起使用,虽然它更接近我想象的,但它似乎是错误的",而且我甚至不好意思写下面的代码(filter
&#39}的文档,甚至说谓词应该没有副作用):
(defn distinct''' [coll]
(let [seen (atom #{})]
(filter (fn [x]
(if (contains? @seen x)
false
(do (swap! seen conj x)
true)))
coll)))
我的问题:
有没有办法以干净的方式对序列库进行这样的延迟状态处理(即使对于这个distinct
示例)?或者,lazy-seq
通常是最干净的方法吗?
答案 0 :(得分:1)
我喜欢distinct'
,虽然我会内联process
辅助函数,例如((fn process ...) coll #{})
。使用lazy-seq和递归来解决问题没有错,并且通过将所有内容都放入map
/ filter
来尝试避免它们会导致程序的可读性降低。
如果你不介意拉平地/有用,你可以用lazy-loop使它更漂亮:
(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)))))))
哪个宏扩展到等同于distinct'
。