寻找最小的解集(如果存在)(两个乘数)

时间:2019-07-05 14:17:30

标签: python algorithm set constraints set-theory

注意:这是this problem的2倍变体形式

给出一个集合A,该集合由介于0.0和1.0之间的浮点数组成,找到一个最小的集合B,以便对于a中的每个A,都有一个值a == B[x],或者a == B[x] * B[y]处有一对唯一值。

例如,给定

$ A = [0.125, 0.25, 0.5, 0.75, 0.9]

B的一种可能(但可能不是最小)的解决方案是

$ B = solve(A)
$ print(B)
[0.25, 0.5, 0.75, 0.9]

这满足了最初的问题,因为A[0] == B[0] * B[1]A[1] == B[1]等使我们可以重新创建原始集合AB的长度比A的长度小,但我猜答案也较小。

我假设B的解决方案空间很大,如果不是无限的话。如果存在解决方案,将如何找到最小集B


注意:

  • 我们并不仅限于A中的项目。B可以由任何一组值组成,无论它们是否存在于A中。
  • 由于A中的项目都是0-1浮点数,因此我假设B也会是0-1浮点数。是这样吗?
  • 这可能是一个约束满足问题,但我不确定如何定义?
  • 由于浮点数学通常是有问题的,因此任何答案都应围绕有理数来构造算法。

2 个答案:

答案 0 :(得分:2)

排序数组。对于每对元素Am,An∈A,m

检查该比率是否等于A中的某个元素,该元素既不等于Am也不等于An。

示例:

A = { 0.125, 0.25, 0.5, 0.75, 0.9 }

(0.125, 0.25): 0.5    <--- bingo
(0.125, 0.5 ): 0.25   <--- bingo
(0.125, 0.75): 0.1(6)
(0.125, 0.9 ): 0.13(8)
(0.25 , 0.5 ): 0.5
(0.25 , 0.75): 0.(3)
(0.25 , 0.9 ): 0.2(7)
(0.5  , 0.75): 0.(6)
(0.5  , 0.9 ): 0.(5) 
(0.75 , 0.9 ): 0.8(3)

分子(0.125)是冗余(= 0.25 * 0.5)或(= 0.5 * 0.25)

我们可以通过引入新元素来做得更好:

另一个例子:

A = { 0.1, 0.11, 0.12, 0.2, 0.22, 0.24 }

(0.1 , 0.11): 0.(90)        ***
(0.1 , 0.12): 0.8(3)        +++
(0.1 , 0.2 ): 0.5     <--------
(0.1 , 0.22): 0.(45)
(0.1 , 0.24): 0.41(6)
(0.11, 0,12): 0.91(6)       ~~~
(0.11, 0.2 ): 0.55
(0.11, 0.22): 0.5     <--------
(0.11, 0.24): 0.458(3)
(0.12, 0.2 ): 0.6
(0.12, 0.22): 0.(54)
(0.12, 0.24): 0.5     <--------
(0.2 , 0.22): 0.(90)        ***
(0.2 , 0.24): 0.8(3)        +++
(0.22. 0.24): 0.91(6)       ~~~

任意两对或多对具有相同比率f的(a1,a2),(a3,a4),(...,...)可用{a1,a3,...,f}替换。

因此,将我们的集合加0.5会使{0.1,0.11,0.12}成为多余。

B = (0.2, 0.22, 0.24, 0.5}

我们现在(我是一般情况)面临一个优化问题,即选择要删除的元素中的哪些以及要添加的因素中的哪些,以最小化B的基数(我留给读者练习) )。

请注意,不需要引入大于1的数字。B也可以表示为{0.1,0.11,0.12,2},但是该集合具有相同的基数。

答案 1 :(得分:0)

Google's OR-Tools提供了一个不错的CP solver,可用于获取解决方案。您可以将问题编码为一组简单的布尔变量,说明哪些变量或变量组合有效。

我首先拉入库的相关部分并设置一些变量:

from ortools.sat.python import cp_model

A = [0.125, 0.25, 0.5, 0.75, 0.9]
# A = [0.1, 0.11, 0.12, 0.2, 0.22, 0.24]

model = cp_model.CpModel()

然后我们可以定义一些帮助程序函数以根据我们的数字创建变量:

vars = {}
def get_var(val):
    assert val >= 0 and val <= 1
    if val in vars:
        return vars[val]

    var = model.NewBoolVar(str(val))
    vars[val] = var
    return var

pairs = {}
def get_pair(pair):
    if pair in pairs:
        return pairs[pair]

    a, b = pair
    av = get_var(a)
    bv = get_var(b)

    var = model.NewBoolVar(f'[{a} * {b}]')
    model.AddBoolOr([av.Not(), bv.Not(), var])
    model.AddImplication(var, av)
    model.AddImplication(var, bv)
    pairs[pair] = var
    return var

get_var(0.5)将创建一个布尔变量(带有Name='0.5'),而get_pair(0.5, 0.8)将创建一个变量并设置约束,以便仅在0.5和0.8均为true时才成立。关于编码boolean logic in ortools

有一个有用的文档

然后我们可以进行A以确定哪些组合是有效的,并将它们添加为求解器的约束:

for i, a in enumerate(A):
    opts = {(a,)}
    for a2 in A[i+1:]:
        assert a < a2
        m = a / a2
        if m == a2:
            opts.add((m,))
        elif m < a2:
            opts.add((m, a2))
        else:
            opts.add((a2, m))

    alts = []
    for opt in opts:
        if len(opt) == 1:
            alts.append(get_var(*opt))
        else:
            alts.append(get_pair(opt))

    model.AddBoolOr(alts)

接下来,我们需要一种说法,我们更喜欢变量为false而不是true。最小的版本是:

model.Minimize(sum(vars.values()))

但是,如果我们稍微复杂一点,并优先考虑A中的值,我们会得到更好的结果:

costsum = 0
for val, var in vars.items():
    cost = 1000 if val in A else 1001
    costsum += var * cost
model.Minimize(costsum)

最后,我们可以运行求解器并打印出解决方案:

solver = cp_model.CpSolver()
status = solver.Solve(model)
print(solver.StatusName(status))

if status in {cp_model.FEASIBLE, cp_model.OPTIMAL}:
    B = [val for val, var in vars.items() if solver.Value(var)]
    print(sorted(B))

这给了我预期的以下结果: [0.125, 0.5, 0.75, 0.9][0.2, 0.22, 0.24, 0.5]  顶部的两个例子

您还可以对以下事实进行编码:您只有在解算器中使用|B| < |A|时,才认为解决方案有效,但是我很想在外部进行这项工作