【问题标题】:How to define the partitions (factorizations w.r.t. concatenation) of a sequence as a lazy sequence of lazy sequences in Clojure如何在 Clojure 中将序列的分区(分解 w.r.t. 连接)定义为惰性序列的惰性序列
【发布时间】:2019-09-16 18:24:02
【问题描述】:

我是 Clojure 的新手,我想定义一个函数 pt,将一个数字 n 和一个序列 s 作为参数,并在 n 部分中返回 s 的所有分区,即它的分解关于n-concatenation。例如(pt 3 [0 1 2]) 应该产生:

(([] [] [0 1 2]) ([] [0] [1 2]) ([] [0 1] [2]) ([] [0 1 2] []) ([0] [] [1 2]) ([0] [1] [2]) ([0] [1 2] []) ([0 1] [] [2]) ([0 1] [2] []) ([0 1 2] [] []))

顺序不重要。 具体来说,我希望结果是惰性向量序列的惰性序列。

我对这种功能的第一次尝试如下:

(defn pt [n s]
  (lazy-seq
    (if (zero? n)
      (when (empty? s) [nil])
      ((fn split [a b]
         (concat
           (map (partial cons a) (pt (dec n) b))
           (when-let [[bf & br] (seq b)] (split (conj a bf) br))))
       [] s))))

之后,我编写了一个不太简洁的版本,通过避免对 1 部分分区进行无用比较来降低时间复杂度,如下所示:

(defn pt [n s]
  (lazy-seq
    (if (zero? n)
      (when (empty? s) [nil])
      ((fn pt>0 [n s]
         (lazy-seq
           (if (= 1 n)
             [(cons (vec s) nil)]
             ((fn split [a b]
                (concat
                  (map (partial cons a) (pt>0 (dec n) b))
                  (when-let [[bf & br] (seq b)] (split (conj a bf) br))))
              [] s))))
       n s))))

这些解决方案的问题在于,尽管它们有效,但它们会产生一系列(非惰性)缺点的惰性序列,我怀疑必须采取完全不同的方法来实现“内在惰性”。因此,欢迎任何更正、建议、解释!

编辑:在阅读 l0st3d 的回答后,我想我应该明确指出,我不希望分区只是成为 LazySeq,而是“真正懒惰”,即计算一部分并且仅在请求时保存在内存中。 例如,下面给出的两个函数都会生成 LazySeq,但只有第一个函数会生成“非常惰性”的序列。

(defn f [n]
  (if (neg? n)
    (lazy-seq nil)
    (lazy-seq (cons n (f (dec n))))))
(defn f [n]
  (if (neg? n)
    (lazy-seq nil)
    (#(lazy-seq (cons n %)) (f (dec n)))))

所以映射(partial concat [a])#(lazy-seq (cons a %)) 而不是(partial cons a) 并不能解决问题。

【问题讨论】:

  • 为什么你需要内部 seq 是惰性的,尤其是?只是出于兴趣...
  • 如果您担心缺点懒惰,请查看此答案:stackoverflow.com/a/12390331
  • 一定要看clojure.math.combinatorics:github.com/clojure/math.combinatorics
  • @pete23:抱歉回复晚了。简短的回答是“主要出于好奇”(因为我认为这是一个有趣的问题,无论其用途如何)。话虽如此,内在的懒惰提供了一些小的空间/时间优化。在我的情况下(稍微简化一下),对于分区的每个部分,都会调用相应的谓词(递归地进行类似的计算)并将短路和/或应用于结果。虽然调用是懒惰的,但部分是急切地计算并占用空间......
  • ... 另请注意,序列的分区与序列占用几乎相同的空间这一事实只是巧合,对于具有串联的序列的幺半群有效。作为一个反例,考虑加法的自然数。事实上,我的解决方案可以推广到某些类幺半群,如果你有兴趣,我可以画出草图。

标签: clojure functional-programming concatenation factorization lazy-sequences


【解决方案1】:

split inline fn 中的 cons 调用是唯一引入渴望的地方。你可以用懒惰地构造一个列表的东西来代替它,比如concat

(defn pt [n s]
  (lazy-seq
   (if (zero? n)
     (when (empty? s) [nil])
     ((fn split [a b]
        (concat
         (map (partial concat [a]) (pt (dec n) b))
         (when-let [[bf & br] (seq b)] (split (conj a bf) br))))
      [] s))))

(every? #(= clojure.lang.LazySeq (class %)) (pt 3 [0 1 2 3])) ;; => true

但是,阅读代码我觉得它相当 unClojurey,我认为这与递归的使用有关。通常你会使用reductionspartition-bysplit-at 之类的东西来做这种事情。我觉得还应该有一种方法可以使它成为一个转换器并将惰性从处理中分离出来(所以你可以使用sequence 说你想要它惰性),但我没有时间解决这个问题现在。我会尽快尝试并提供更完整的答案。

【讨论】:

  • 虽然这样你会得到内心的懒惰,但它并不是那么“有效”。 (在询问之前,我通过映射#(lazy-seq (cons a %)) 进行了类似的尝试。)问题是,无论您请求分区的第一部分还是全部,都会对 pt 和 split 进行相同数量的调用并且相同空间被占用。正如我在问题中所说,我认为需要不同的算法方案......
  • ... 至于 unclojureiness,有一个相对通用、数学上清晰和简洁的解决方案,不依赖于 Clojure 的大部分特定机制,但使用它的惰性序列,对我来说是有价值的。 (另请参阅我对 pete23 评论的回复,其中提到了概括。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 2010-12-08
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多