我有一些数字:
(#{7 1} #{3 5} #{6 3 2 5}
#{0 7 1 8} #{0 4 8} #{7 1 3 5}
#{6 2} #{0 3 5 8} #{4 3 5}
#{4 6 2} #{0 6 2 8} #{4} #{0 8}
#{7 1 6 2} #{7 1 4})
我希望将每个集合变成一个四个数字的向量,这样所有向量的总和加起来为16,它们只能来自这组数字:
#{7 1} => [1 1 7 7]
#{4 3 5} => [3 4 4 5]
#{4} => [4 4 4 4]
#{0 8} => [0 0 8 8]
最后,向量必须包含集合中的所有数字。对于abitrary向量长度来说,解决这个问题会很棒:)
如何编写clojure代码。
答案 0 :(得分:6)
使用幼稚搜索很容易处理
(defn bag-sum [s n]
(for [a s, b s, c s, d s
:let [v [a b c d]]
:when (= n (apply + v))
:when (= (set v) s)]
v))
(take 1 (bag-sum #{7 1} 16)) ;=> ([7 7 1 1])
(take 1 (bag-sum #{3 5} 16)) ;=> ([3 3 5 5])
(take 1 (bag-sum #{4 3 5} 16)) ;=> ([4 4 3 5])
即使没有设置约束,搜索空间也很小。
(require '[clojure.math.combinatorics :refer [partition]])
(count (partitions (repeat 16 1))) ;=> 231
所以,一个天真的解决方案再次非常实用。我们将生产所有长度的解决方案,可根据需要进一步过滤。如果输入集中有零,它可以填充任何解决方案。
(defn bag-sum16 [s]
(for [p (partitions (repeat 16 1))
:let [v (mapv (partial apply +) p)]
:when (= (set v) s)]
v))
第一个例子有2个解决方案 - 长度4和长度10。
(bag-sum16 #{7 1}) ;=> ([7 7 1 1] [7 1 1 1 1 1 1 1 1 1])
(bag-sum16 #{3 5}) ;=> ([5 5 3 3])
(bag-sum16 #{3 4 5}) ;=> ([5 4 4 3])
s
,输出长度m
和总和n
这仍然相当天真,但在超过目标总和时修剪搜索树。我是core.logic的新手,所以这是一次练习的机会,而不是尝试最好地表达问题。这比上面的小空间上的天真解决方案更糟糕,但能够在某些中等大小的情况下进行计算。
(defn bag-sum-logic [s m n]
(let [m* (- m (count s))
n* (- n (apply + s))
nums (vec (repeatedly m* lvar))
sums (concat [0] (repeatedly (dec m*) lvar) [n*])
dom (apply fd/domain (sort s))
rng (fd/interval n*)
sol (run 1 [q]
(== q nums)
(everyg #(fd/in % dom) nums)
(everyg #(fd/in % rng) sums)
(everyg #(apply fd/+ %)
(map cons nums (partition 2 1 sums))))]
(when (seq sol) (sort (concat s (first sol))))))
(bag-sum-logic #{7 1} 4 16) ;=> (1 1 7 7)
(bag-sum-logic #{7 1} 10 16) ;=> (1 1 1 1 1 1 1 1 1 7)
(bag-sum-logic #{3 5} 4 16) ;=> (3 3 5 5)
(bag-sum-logic #{3 4 5} 4 16) ;=> (3 4 4 5)
(time (bag-sum-logic #{3 4 5} 30 100))
;=> "Elapsed time: 18.739627 msecs"
;=> (3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 5 5 5 5)
这个问题是linear Diophantine equation, which can be solved with the Extended Euclidean Algorithm通过矩阵单模式行减少,即在一列中执行欧几里得算法,同时使整个基准行一起用于乘坐。
例如,在#{3 5}和总和16的情况下,您想要求解等式
3x + 5y = 16
受限于x > 0
,y > 0
和x + y = 4
(您的示例)的其他限制。
矩阵和缩减步骤
[[3 1 0] -> [[3 1 0] -> [[1 2 -1] -> [[1 2 -1]
[5 0 1]] [2 -1 1]] [2 -1 1]] [0 -5 3]]
所以3和5的GCD是1,它分为16个。因此在约束之前有(无限多个)解决方案
x = 16 * 2 - 5k
y = 16 * -1 + 3k
由于我们需要x + y = 4
,4 = 16 - 2k
,因此需要k = 6
,所以
x = 2
y = 2
我们需要2份3份和2份5份。
这以相同的方式推广到2个以上的变量。但是对于2个变量,解决方案的长度完全约束单个自由变量,如上所示,可以指定3个以上的变量。
求解线性丢番图方程可以在多项式时间内完成。但是,一旦你添加了边界(0, m)
,找到一个解决方案就会变成NP完全,尽管快速阅读research results表明存在相当容易处理的方法。
答案 1 :(得分:1)
假设您只需要每个解决方案一个解决方案,并且您希望解决方案按照您的示例按升序排序,这就是我想出的。 1-4组数组的组合并不多,所以我最初分解问题的方法是看看可能的解决方案的模式是什么样的。
(def x #{3 5})
(def g 16)
(def y {1 [[0 0 0 0]]
2 [[0 0 0 1][0 0 1 1][0 1 1 1]]
3 [[0 0 1 2][0 1 1 2][0 1 2 2]]
4 [[0 1 2 3]]})
此映射的此键指示正在评估的集x
的大小。一旦将值分类到向量中,这些值就是该集合的索引的可能排列。现在我们可以根据集合的大小选择排列并计算每个排列的值,一旦达到目标就停止:
(filter #(= g (apply + %))
(for [p (y (count x))]
(mapv #((into [] (sort x)) %) p)))
排列上方的地图的每个键的值形成一个模式:第一个索引始终为0,最后一个始终是设置大小 - 1并且所有值都与左边的值相同或者高于左边的值。因此,上面的地图可以推广为:
(defn y2 [m s]
(map (fn [c] (reduce #(conj %1 (+ %2 (peek %1))) [0] c))
(clojure.math.combinatorics/permutations
(mapv #(if (>= % (dec s)) 0 1) (range (dec m))))))
(def y (partial y2 4))
过滤器现在可以使用最多s
的任意数量的设置项。在对输入集进行排序时,可以通过对log2n搜索时间的可能解决方案的排列进行二进制搜索来优化搜索以找到正确(或否)的解决方案。