具有潜在无限序列的有限序列的笛卡尔积

时间:2014-09-27 12:46:24

标签: clojure functional-programming lazy-evaluation

问题

我需要创建一个函数,当给定有限序列的潜在无限序列时,它产生的序列是它们的“笛卡尔积”。

即。给出序列

'((1 2) (3 4))

函数产生(某些排序):

'((1 3) (1 4) (2 3) (2 4)

重要,对于笛卡尔积p列表中的每个ps,必须有一些自然数n(= p (last (take n ps)))。或者,非正式地,您只需要迭代序列一个有限的量来到达其中的任何元素。

处理无限列表时,这种情况变得很重要。

Haskell中的解决方案

在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中的解决方案

所以我试图在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的懒惰序列模型是如何工作的,这是主要的问题。

2 个答案:

答案 0 :(得分:1)

所以,我明白了。问题是一个微妙但令人沮丧的问题。问题源于我执行的解构,基本上是每个函数:我使用这种习语:[x & xs*] (seq xs),然而,这实现了xs*的第一个元素,以及实现x 。此行为类似于您使用firstnext分别获取列表的头部和尾部时所看到的行为。

使用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)

...或多或少地立即返回。


备注

  • Knuth 或其他地方做得更好吗?我无权访问 它。
  • 不需要保留最后一个未用尽的序列,因为什么都没有 否则使用它。