【发布时间】:2014-01-03 22:39:52
【问题描述】:
我希望生成一个集合的所有子集,除了空集
即
(all-subsets #{1 2 3}) => #{#{1},#{2},#{3},#{1,2},#{2,3},#{3,1},#{1,2,3}}
如何在 clojure 中做到这一点?
【问题讨论】:
-
通常幂集也包含空集。
标签: clojure set subset combinatorics
我希望生成一个集合的所有子集,除了空集
即
(all-subsets #{1 2 3}) => #{#{1},#{2},#{3},#{1,2},#{2,3},#{3,1},#{1,2,3}}
如何在 clojure 中做到这一点?
【问题讨论】:
标签: clojure set subset combinatorics
在您的:dependencies 中project.clj:
[org.clojure/math.combinatorics "0.0.7"]
在 REPL:
(require '[clojure.math.combinatorics :as combinatorics])
(->> #{1 2 3}
(combinatorics/subsets)
(remove empty?)
(map set)
(set))
;= #{#{1} #{2} #{3} #{1 2} #{1 3} #{2 3} #{1 2 3}}
clojure.math.combinatorics/subsets 明智地返回一个 seq 的 seq,因此需要额外的转换来匹配您想要的输出。
【讨论】:
math.combinatorics 这样的官方库没有任何取舍,而且通常在很大程度上优于您可以手动完成的所有操作。如果可以改进,您也可以期待致力于解决该问题的人对其进行改进。
这是一个简洁的尾递归版本,仅依赖于 clojure.core。
(defn power [s]
(loop [[f & r] (seq s) p '(())]
(if f (recur r (concat p (map (partial cons f) p)))
p)))
如果您想要一组集合中的结果,请使用以下内容。
(defn power-set [s] (set (map set (power s))))
【讨论】:
@zcaudate:为了完整起见,这里是一个递归实现:
(defn subsets
[s]
(if (empty? s)
#{#{}}
(let [ts (subsets (rest s))]
(->> ts
(map #(conj % (first s)))
(clojure.set/union ts)))))
;; (subsets #{1 2 3})
;; => #{#{} #{1} #{2} #{3} #{1 2} #{1 3} #{2 3} #{1 2 3}} (which is correct).
【讨论】:
这是@Brent M. Spell 解决方案的一个细微变化,目的是在惯用的 Clojure 中寻求对性能考虑的启发。
我只是想知道在循环中构造子集而不是通过(map set ...) 进行另一次迭代是否会节省一些开销,尤其是当集合非常大时?
(defn power [s]
(set (loop [[f & r] (seq s) p '(#{})]
(if f (recur r (concat p (map #(conj % f) p)))
p))))
(power [1 2 3])
;; => #{#{} #{3} #{2} #{1} #{1 3 2} #{1 3} #{1 2} #{3 2}}
在我看来loop 和recur 并不懒惰。
最好有一个像 Brent 那样的惰性求值版本,以保持表达式的优雅,同时使用惰性来实现效率。
这个版本作为一个框架还有另一个优势,即当有太多子集需要计算时,它可以轻松地支持对候选子集进行修剪。可以在conj的位置添加剪枝逻辑。我用它来实现“频繁项集”的先验算法。
【讨论】:
参考:Algorithm to return all combinations of k elements from n
(defn comb [k l]
(if (= 1 k) (map vector l)
(apply concat
(map-indexed
#(map (fn [x] (conj x %2))
(comb (dec k) (drop (inc %1) l)))
l))))
(defn all-subsets [s]
(apply concat
(for [x (range 1 (inc (count s)))]
(map #(into #{} %) (comb x s)))))
; (所有子集 #{1 2 3})
; (#{1} #{2} #{3} #{1 2} #{1 3} #{2 3} #{1 2 3})
【讨论】:
此版本大致仿照ES5 version on Rosetta Code。我知道这个问题似乎已经合理地解决了......但无论如何,你去吧。
(fn [s]
(reduce
(fn [a b] (clojure.set/union a
(set (map (fn [y] (clojure.set/union #{b} y)) a))))
#{#{}} s))
【讨论】: