我试图实现,将采取列表的列表,并返回这些列表的笛卡儿积的方法。
这是我到目前为止有:
(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的和函数式编程我可能是完全错误的轨道上。请帮忙! :)
这是一个更容易做一个换理解不是试图通过手动制定出递归:
(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)))
我会检查https://github.com/clojure/math.combinatorics它有
(组合/笛卡尔积[1 2] [3 4]);; =>((3:1)(4:1)(2 3)(2 4))
为了比较的缘故,在原有的精神
(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))
我知道我迟到了 - 我只是想补充一个不同的方法,对完整性的缘故。
相比于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
就个人而言,我会用amalloy的for
解决方案。我的一般经验法则是,如果我的环可以表示为一个单一的map
/ filter
/等用一个简单的函数参数(这样的函数名称或短fn
/ #()
形式),它能够更好地使用该功能调用。一旦它得到比这更复杂,一个for
表达更容易阅读。特别是,for
远不如嵌套地图更好。这就是说,如果我没有在这里使用for
,这是我怎么会写的函数:
(defn cart
([] '(()))
([xs & more]
(mapcat #(map (partial cons %)
(apply cart more))
xs)))
注意事项:首先,没有必要为减少。递归可以处理它就好了。
第二,只有两种情况。我们可以调用函数就好了一个空的列表上,所以我们关心的是空与非空。
第三,作为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))
对于大多数的目的阿兰的回答是伟大的,因为你会得到一个懒惰的理解,并为您实现其成员懒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)))))