【发布时间】: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