Clojure 将数字集添加到16

Clojure 将数字集添加到16,clojure,permutation,Clojure,Permutation,我有几组数字: (#{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}) #{7 1} => [1 1 7 7] #{4 3 5} => [3 4 4 5] #{4} => [4 4 4 4] #{0 8} => [0

我有几组数字:

(#{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})
 #{7 1}   => [1 1 7 7]
 #{4 3 5} => [3 4 4 5]
 #{4}     => [4 4 4 4]
 #{0 8}   => [0 0 8 8]
我希望将每一组向量都变成一个四位数的向量,这样所有向量的总和加起来就是16,它们只能来自于这组数字:

(#{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})
 #{7 1}   => [1 1 7 7]
 #{4 3 5} => [3 4 4 5]
 #{4}     => [4 4 4 4]
 #{0 8}   => [0 0 8 8]
最后,向量必须包含集合中的所有数字。对于任意向量长度,解决这个问题将非常好:)


clojure代码是如何编写的。

假设每个集合只需要一个解决方案,并且希望解决方案按照您的示例按升序排列,这就是我提出的。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搜索时间的可能解决方案的排列进行二进制搜索来解决问题。

假设每个集合只需要一个解决方案,并且希望解决方案按照示例升序排列。这就是我提出的。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搜索时间的可能解的排列进行二进制搜索来优化搜索,以找到正确(或否)的解。

使用小集合和最初规定的输出长度4 这很容易通过简单的搜索处理

(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))


假设16是固定的,所有数字都是非负的 即使没有设置约束,搜索空间也很小

(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))

第一个示例有两个解决方案-长度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])

使用core.logic finite domains使用任意但指定的域集
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))))))


一般情况下更好的算法? 该问题是一个via矩阵单模行缩减问题,即在一列中执行欧几里德算法,同时将整个基行用于骑乘

例如,在#{35}和sum 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完全问题,尽管快速阅读一下,你会发现有相当容易处理的方法。

使用小集合和最初规定的输出长度4 这很容易通过简单的搜索处理

(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))


假设16是固定的,所有数字都是非负的 即使没有设置约束,搜索空间也很小

(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))

第一个示例有两个解决方案-长度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])

使用core.logic finite domains使用任意但指定的域集
s
、输出长度
m
、和
n
这仍然是相当幼稚的,但是当超过目标和时会修剪搜索树。我是core.logic的新手,所以这更多的是一个练习的机会,而不是试图最好地表达问题。这