14

我正在尝试实现一种方法,该方法将获取列表并返回这些列表的笛卡尔积。

这是我到目前为止所拥有的:

(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 和函数式编程还很陌生,所以我可能走在完全错误的轨道上。请帮忙!:)

4

6 回答 6

26

作为一种理解,这比尝试手动计算递归要容易得多:

(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)))
于 2013-08-15T07:23:26.403 回答
11

I would check https://github.com/clojure/math.combinatorics it has

(combo/cartesian-product [1 2] [3 4]) ;;=> ((1 3) (1 4) (2 3) (2 4))

于 2013-10-15T11:01:50.553 回答
10

为了比较起见,本着原著的精神

(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))
于 2013-08-15T15:21:37.277 回答
7

我知道我迟到了——为了完整起见,我只是想添加一种不同的方法。

与 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这里需要引用。

在线演示

; 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
于 2014-10-25T20:19:21.840 回答
5

就个人而言,我会使用 amalloy 的for解决方案。我的一般经验法则是,如果我的循环可以表示为带有简单函数参数的单个map/ /etc 调用(因此是函数名称或短/形式),则最好使用该函数。一旦它变得比这更复杂,表达式就会更容易阅读。尤其是比嵌套地图要好得多。也就是说,如果我不在这里使用,这就是我编写函数的方式:filterfn#()forforfor

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

注意事项:首先,不需要reduce。递归可以很好地处理它。

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

第三,正如 amalloy 所解释的, 的正确(cart)值为'(())。这实际上是相当微妙的,当我编写这样的函数时,我确实把它搞砸了。如果您非常仔细地浏览一个简单的案例,您应该能够看到为什么该值使递归起作用。

第四,我一般不喜欢用fn. 这更多是个人喜好,但我总是使用#(), partial,或者comp如果我可以摆脱它。 #()对于较小的功能绝对是惯用的,尽管其他两个不太常见。

第五,一些风格说明。最大的问题是缩进。这里最好的建议是找到一个自动缩进 lisp 代码的编辑器。自动缩进是编辑器提供的最重要的东西之一,因为当你的括号不匹配时,它会变得非常明显。此外,关闭括号永远不会fn单独使用,除非您计划递归,否则 s 不需要内部名称,而且我通常比您有更多的换行符。我喜欢认为我上面的代码风格相当体面,作为另一个例子,我将如何格式化您的代码:

(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))
于 2013-08-15T21:25:29.773 回答
0

对于大多数目的,艾伦的答案很好,因为你得到了一个懒惰的理解,并且当你意识到它的成员时,一个懒惰的 seq 不会导致堆栈溢出,即使你不使用 (recur)。

我有兴趣尝试使用显式递归来制作尾递归版本,这不仅是因为懒惰对我的应用程序没有任何帮助,而且还为了好玩和咯咯笑:

(defn cartesian-product
  ([cols] (cartesian-product '([]) cols))
  ([samples cols]
    (if (empty? cols)
      samples
      (recur (mapcat #(for [item (first cols)]
                        (conj % item)) samples)
             (rest cols)))))
于 2015-08-25T14:04:34.240 回答