我需要创建一个函数,当给定有限序列的潜在无限序列时,它产生的序列是它们的“笛卡尔积”。
即。给出序列
'((1 2) (3 4))
函数产生(某些排序):
'((1 3) (1 4) (2 3) (2 4)
重要,对于笛卡尔积p
列表中的每个ps
,必须有一些自然数n
,(= p (last (take n ps)))
。或者,非正式地,您只需要迭代序列一个有限的量来到达其中的任何元素。
处理无限列表时,这种情况变得很重要。
在Haskell中,我就是这样做的:
interleave :: [a] -> [a] -> [a]
interleave [] ys = ys
interleave (x:xs) ys = x : interleave ys xs
combine :: [[a]] -> [[a]]
combine = foldr prod [[]]
where
prod xs css = foldr1 interleave [ [x:cs | cs <- css] | x <- xs]
并且调用它可以获得以下内容:
combine [[0..] [0..]] = [[0,0,0],[1,0,0],[,1,0],[2,0,0],[0,0,1],[1,1,0],...
所以我试图在Clojure中复制它,就像这样,(它几乎是一个直接翻译):
(defn interleave
"Returns a lazy sequence of the interleavings of sequences `xs` and `ys`
(both potentially infinite), leaving no elements discarded."
[xs ys]
(lazy-seq
(if-let [[x & xs*] (seq xs)]
(cons x (interleave ys xs*))
ys)))
(defn interleave*
"Converts a sequence of potentially infinite sequences into its lazy
interleaving."
[xss]
(lazy-seq
(when-let [[xs & xss*] (seq xss)]
(interleave xs (interleave* xss*)))))
(defn combine
"Takes a finite sequence of potentially infinite sequences, and combines
them to produce a possibly infinite sequence of their cartesian product."
[xss]
(if-let [[xs & xss*] (seq xss)]
(interleave*
(for [x xs]
(for [cs (combine xss*)]
(lazy-seq (cons x cs)))))
'(()) ))
但是当我跑步时:
(take 1 (combine [(range) (range)]))
我明白了:
StackOverflowError cfg.util/combine/iter--3464--3468/fn--3469/fn--3470/iter--3471--3475/fn--3476
那么,如何让它足够懒,以避免堆栈溢出?真的,我不明白Clojure的懒惰序列模型是如何工作的,这是主要的问题。
答案 0 :(得分:1)
所以,我明白了。问题是一个微妙但令人沮丧的问题。问题源于我执行的解构,基本上是每个函数:我使用这种习语:[x & xs*] (seq xs)
,然而,这实现了xs*
的第一个元素,以及实现x
。此行为类似于您使用first
和next
分别获取列表的头部和尾部时所看到的行为。
使用first
/ rest
而不是以这种方式解构,修复了堆栈溢出:
(defn interleave
"Returns a lazy sequence of the interleavings of sequences `xs` and `ys`
(both potentially infinite), leaving no elements discarded."
[xs ys]
(lazy-seq
(if-let [xs* (seq xs)]
(cons (first xs*) (interleave ys (rest xs*)))
ys)))
(defn interleave*
"Converts a sequence of potentially infinite sequences into its lazy
interleaving."
[xss]
(lazy-seq
(when-let [xss* (seq xss)]
(interleave (first xss*)
(interleave* (rest xss*))))))
(defn combine
"Takes a finite sequence of potentially infinite sequences, and combines
them to produce a possibly infinite sequence of their cartesian product."
[xss]
(if-let [xss* (seq xss)]
(interleave*
(for [x (first xss*)]
(for [cs (combine (rest xss*))]
(lazy-seq (cons x cs)))))
'(()) ))
运行它,我们得到:
(= (take 5 (combine [(range) (range) (range)]))
'((0 0 0) (1 0 0) (0 1 0) (2 0 0) (0 0 1)))
答案 1 :(得分:1)
我认为你的解决方案可能在算法上难以处理,一次又一次地重建子序列,就像简单的Fibonacci函数一样:
(defn fib [n]
(case n
(0N 1N) n
(+ (fib (- n 1)) (fib (- n 2)))))
...重新计算其先例。
无论如何,在[100 10]
和(range)
的笛卡尔积中搜索(range)
:
(first (filter #{[100 10]} (combine [(range) (range)])))
......在合理的时间内没有回来。
我可以为您提供更快但更不优雅的解决方案。
首先,有几个实用程序:
Something from @amalloy计算有限序列的笛卡尔乘积:
(defn cart [colls]
(if (empty? colls)
'(())
(for [x (first colls)
more (cart (rest colls))]
(cons x more))))
改编自 Clojure Cookbook 的函数来映射地图的值:
(defn map-vals [f m] (zipmap (keys m) (map f (vals m))))
现在我想要的功能,我称之为enum-cart
,因为它列举了笛卡尔积,即使是无限序列:
(defn enum-cart [colls]
(let [ind-colls (into (sorted-map) (map-indexed (fn [n s] [n (seq s)]) colls))
entries ((fn tins [ss] (let [ss (select-keys ss (map key (filter val ss)))]
(lazy-seq
(if (seq ss)
(concat
(map-vals first ss)
(tins (map-vals next ss)))))))
ind-colls)
seens (reductions
(fn [a [n x]] (update-in a [n] conj x))
(vec (repeat (count colls) []))
entries)]
(mapcat
(fn [sv [n x]] (cart (assoc sv n [x])))
seens entries)))
这个想法是生成一个索引的条目序列,绕过非耗尽的序列。由此我们生成了我们已经从每个序列中看到的伴随序列。我们将这两者结合起来,生成新元素的免费笛卡尔积与我们对其他序列的积分。答案是这些免费产品的连接。
例如
(enum-cart [(range 3) (range 10 15)])
...生产
((0 10)
(1 10)
(0 11)
(1 11)
(2 10)
(2 11)
(0 12)
(1 12)
(2 12)
(0 13)
(1 13)
(2 13)
(0 14)
(1 14)
(2 14))
并且
(first (filter #{[100 10]} (enum-cart [(range) (range)])))
;(100 10)
...或多或少地立即返回。
备注的