Clojure中的笛卡尔乘积

问题描述 投票:12回答:6

我试图实现,将采取列表的列表,并返回这些列表的笛卡儿积的方法。

这是我到目前为止有:

(defn cart


([] '())
 ([l1] (map list l1))
 ([l1 l2] 
  (map 
    (fn f[x] (map
    (fn g [y] (list x y))
    l2))
      l1)
      )

)

(defn cartesian-product [& lists] 
      (reduce cart lists)

 )





;test cases 
(println (cartesian-product '(a b) '(c d))) ; ((a c) (a d) (b c) (b d))
(println (cartesian-product ())) ;()
(println (cartesian-product '(0 1)))    ; ((0) (1))
(println (cartesian-product '(0 1) '(0 1))) ; ((0 0) (0 1) (1 0) (1 1))
(println (apply cartesian-product (take 4 (repeat (range 2))))) ;((0 0 0 0) (0 0 0 1) (0 0 1 0) (0 0 1 1) (0 1 0 0) (0 1 0 1) (0 1 1 0) (0 1 1 1) (1 0 0 0) (1 0 0 1) (1 0 1 0) (1 0 1 1) (1 1 0 0) (1 1 0 1) (1 1 1 0) (1 1 1 1))

问题是我的解决方案是真正的“支架”。

(((a c) (a d)) ((b c) (b d)))
()
(0 1)
(((0 0) (0 1)) ((1 0) (1 1)))
(((((((0 0) (0 1)) 0) (((0 0) (0 1)) 1)) 0) (((((0 0) (0 1)) 0) (((0 0) (0 1)) 1)) 1)) ((((((1 0) (1 1)) 0) (((1 0) (1 1)) 1)) 0) (((((1 0) (1 1)) 0) (((1 0) (1 1)) 1)) 1)))

我尝试添加

      (apply concat(reduce cart lists))

但后来我得到像这样崩溃:

((a c) (a d) (b c) (b d))
()
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:494)

所以,我觉得我很接近,但缺少的东西。不过,因为我很新Clojure的和函数式编程我可能是完全错误的轨道上。请帮忙! :)

clojure functional-programming cartesian-product
6个回答
21
投票

这是一个更容易做一个换理解不是试图通过手动制定出递归:

(defn cart [colls]
  (if (empty? colls)
    '(())
    (for [more (cart (rest colls))
          x (first colls)]
      (cons x more))))

user> (cart '((a b c) (1 2 3) (black white)))
((a 1 black) (a 1 white) (a 2 black) (a 2 white) (a 3 black) (a 3 white) 
 (b 1 black) (b 1 white) (b 2 black) (b 2 white) (b 3 black) (b 3 white) 
 (c 1 black) (c 1 white) (c 2 black) (c 2 white) (c 3 black) (c 3 white))

基本情况是显而易见的(它需要包含空列表的列表,而不是空列表本身,因为是拿不出名单的笛卡尔积单程)。在递归的情况下,你只需遍历第一个集合中的每个元素x,然后在列表的其余部分的每个笛卡尔积,前面加上您所选择的x

请注意,它写的for理解的两个分句在这个略显不自然的顺序是非常重要的:交换它们导致大幅放缓。这样做的原因是为了避免重复工作。第二结合的主体将再次在第一个结合,它(如果你写在错误的顺序条款)将意味着昂贵的递归条款的多次浪费副本的每个项目进行评估。如果你希望要格外小心,你可以把它清楚地表明这两个分句都是独立的,而是通过写:

(let [c1 (first colls)]
  (for [more (cart (rest colls))
        x c1]
    (cons x more)))

8
投票

我会检查https://github.com/clojure/math.combinatorics它有

(组合/笛卡尔积[1 2] [3 4]);; =>((3:1)(4:1)(2 3)(2 4))


7
投票

为了比较的缘故,在原有的精神

(defn cart 
  ([xs] 
   xs) 
  ([xs ys] 
   (mapcat (fn [x] (map (fn [y] (list x y)) ys)) xs)) 
  ([xs ys & more] 
   (mapcat (fn [x] (map (fn [z] (cons x z)) (apply cart (cons ys more)))) xs)))

(cart '(a b c) '(d e f) '(g h i))
;=> ((a d g) (a d h) (a d i) (a e g) (a e h) (a e i) (a f g) (a f h) (a f i)
;    (b d g) (b d h) (b d i) (b e g) (b e h) (b e i) (b f g) (b f h) (b f i) 
;    (c d g) (c d h) (c d i) (c e g) (c e h) (c e i) (c f g) (c f h) (c f i))

5
投票

我知道我迟到了 - 我只是想补充一个不同的方法,对完整性的缘故。

相比于amalloy的方法,它是懒惰太(参数列表都热切评估,虽然)和稍快时所需的所有结果(我测试他们都与下面的演示代码),但是很容易出现堆栈溢出(很像底层for理解它生成和评价)作为列表的数目增加。另外,请记住,eval有一个限制,它可以传递给代码的大小。

首先考虑问题的一个实例:你想找到[:a :b :c]'(1 2 3)的笛卡尔积。显而易见的解决方案是使用for理解,就像这样:

(for [e1 [:a :b :c]
      e2 '(1 2 3)]
  (list e1 e2))

; ((:a 1) (:a 2) (:a 3) (:b 1) (:b 2) (:b 3) (:c 1) (:c 2) (:c 3))

现在的问题是:是否有可能在与列表中的任意数量的工作方式概括呢?这里的答案是肯定的。这是下面的宏做了什么:

(defmacro cart [& lists]
  (let [syms (for [_ lists] (gensym))]
    `(for [~@(mapcat list syms lists)]
       (list ~@syms))))

(macroexpand-1 '(cart [:a :b :c] '(1 2 3)))

; (clojure.core/for [G__4356 [:a :b :c] 
;                    G__4357 (quote (1 2 3))] 
;   (clojure.core/list G__4356 G__4357))

(cart [:a :b :c] '(1 2 3))

; ((:a 1) (:a 2) (:a 3) (:b 1) (:b 2) (:b 3) (:c 1) (:c 2) (:c 3))

从本质上讲,你让编译器为您生成相应的for理解。这个转换为功能是非常简单的,但有一个小陷阱:

(defn cart [& lists]
  (let [syms (for [_ lists] (gensym))]
    (eval `(for [~@(mapcat #(list %1 `'~%2) syms lists)]
             (list ~@syms)))))

(cart [:a :b :c] '(1 2 3))

; ((:a 1) (:a 2) (:a 3) (:b 1) (:b 2) (:b 3) (:c 1) (:c 2) (:c 3))

被加引号名单被视为函数调用,这就是为什么引用%2有必要在这里。

Online Demo

; https://projecteuler.net/problem=205

(defn cart [& lists]
  (let [syms (for [_ lists] (gensym))]
    (eval `(for [~@(mapcat #(list %1 `'~%2) syms lists)]
             (list ~@syms)))))

(defn project-euler-205 []

  (let [rolls (fn [n d]
                (->> (range 1 (inc d))
                  (repeat n)
                  (apply cart)
                  (map #(apply + %))
                  frequencies))

        peter-rolls (rolls 9 4)
        colin-rolls (rolls 6 6)

        all-results (* (apply + (vals peter-rolls))
                       (apply + (vals colin-rolls)))

        peter-wins (apply + (for [[pk pv] peter-rolls
                                  [ck cv] colin-rolls
                                  :when (> pk ck)]
                              (* pv cv)))]

    (/ peter-wins all-results)))

(println (project-euler-205)) ; 48679795/84934656

4
投票

就个人而言,我会用amalloy的for解决方案。我的一般经验法则是,如果我的环可以表示为一个单一的map / filter /等用一个简单的函数参数(这样的函数名称或短fn / #()形式),它能够更好地使用该功能调用。一旦它得到比这更复杂,一个for表达更容易阅读。特别是,for远不如嵌套地图更好。这就是说,如果我没有在这里使用for,这是我怎么会写的函数:

(defn cart
  ([] '(()))
  ([xs & more]
    (mapcat #(map (partial cons %)
                  (apply cart more))
            xs)))

注意事项:首先,没有必要为减少。递归可以处理它就好了。

第二,只有两种情况。我们可以调用函数就好了一个空的列表上,所以我们关心的是空与非空。

第三,作为amalloy解释,(cart)的正确值是'(())。这实际上是相当微妙的,我确实搞砸当我写这样的功能。如果你通过一个简单的情况下,走得很小心,你应该能够明白为什么值使得递归工作。

第四,我一般不喜欢用fn。这更多的是一种个人喜好,但我总是用#()partialcomp如果我能摆脱它。 #()是小功能绝对地道,尽管其他两个都有点不太常见。

第五,有些款式的笔记。最大的问题是压痕。这里的最好的建议是找到自动缩进Lisp代码的编辑器。自动缩进是为你的编辑器提供了最重要的事情之一,因为它使言自明,当你的括号不匹配。此外,关闭括号从来就自己去行,fns除非你是在递归计划不需要内部名称,我通常有几个换行符比你做的。我这样想,我上面的代码是相当体面的风格,作为另一个例子,这里是我会怎样格式化你的代码:

(defn cart
  ([] '())
  ([l1] (map list l1))
  ([l1 l2] 
    (map (fn [x]
           (map (fn [y]
                  (list x y))
                l2))
         l1)))

(defn cartesian-product [& lists] 
  (reduce cart lists))

0
投票

对于大多数的目的阿兰的回答是伟大的,因为你会得到一个懒惰的理解,并为您实现其成员懒SEQ不会导致堆栈溢出,即使你不使用(复发)。

我很感兴趣,想用明确易复发手艺尾递归版本,而不是其中最重要的,因为懒惰是不会成为我的应用程序的帮助,同时也为乐趣和笑声:

(defn cartesian-product
  ([cols] (cartesian-product '([]) cols))
  ([samples cols]
    (if (empty? cols)
      samples
      (recur (mapcat #(for [item (first cols)]
                        (conj % item)) samples)
             (rest cols)))))
© www.soinside.com 2019 - 2024. All rights reserved.