使用序列库进行延迟状态处理

时间:2014-12-24 22:25:13

标签: clojure

让我们说我想要处理一个序列,其中处理是有状态的,我想使用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 #{})))

我可以使用上述内容,但是,如果可以的话,我更愿意使用mapfilter之类的方法。我正在努力干净地完成这项工作。一种有效的方法是使用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''实现了使用更高级别抽象(mapfilterreductions)同时保持懒惰的目标。但它没有过于复杂,尤其是我将它作为累加器传递的向量。

如果我尝试将状态直接嵌入谓词中以与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通常是最干净的方法吗?

1 个答案:

答案 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'

的内容