【问题标题】:Finding the smallest solution set, if one exists (two multipliers)找到最小的解决方案集,如果存在(两个乘数)
【发布时间】:2019-07-05 14:17:30
【问题描述】:

注意:这是this problem 的两个乘数变体

给定一个集合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 浮点数。是这样吗?
  • 这可能是一个约束满足问题,但我不确定它是如何定义的?
  • 由于浮点数学通常存在问题,因此任何答案都应围绕有理数构建算法。

【问题讨论】:

  • 这个问题的整数对应物在素因数等方面有一个完善的理论。我建议你寻找方法将你的问题转化为涉及整数的问题。然后你也会知道任务的计算复杂度。
  • 使用浮点数可能不会给您预期的结果,除非您试图将数字与非二次幂隔离开来。
  • 整数对于我的应用程序是完全可行的,如果我们将A 的值作为一组百分比,然后将每个百分比乘以 100。(实际上,我将值向下舍入到最接近的 .0001的 A,但如有必要,我可以四舍五入到 0.01。)使用出乎意料的十进制值是什么意思?只是浮点数学限制?我打算使用 Python 的 Decimal 库,我相信它应该可以处理这种精度。
  • 例如在IEEE754双精度中,0.07 * 20不等于1.4。使用整数会起作用。
  • @RobertBaron @saucewaffle Fraction 类可用于在 Python 中精确表示有理数。

标签: python algorithm set constraints set-theory


【解决方案1】:

对数组进行排序。对于每对元素 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)       ~~~

任何 2 对或更多对 (a1,a2), (a3,a4), (... , ...) 具有共同比率 f 可以替换为 { 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} 但这个集合具有相同的基数。

【讨论】:

  • 有趣的方法 - 我认为这可能会解决它。但是,我们如何准确地找到整个集合中构建最小解决方案的所有因素呢?我想会有其他因素的组合可以产生更小的Bs?
  • 令人着迷!我确实认为这可行,但我们怎么知道这是我们能得到的 B 的最小基数呢?我真的没有时间思考这个问题,但是是否可以递归处理 B 并找到更多简化?
  • 如上面的 cmets 中所述,这将遇到浮点数学问题。可能值得添加一个有理数的例子吗?
  • 我选择以重复的十进制表示 (en.wikipedia.org/wiki/Repeating_decimal) 来表示数字,但您也可以将它们存储为分数。两种表述都是准确的。
  • @saucewaffle 如果没有限制bs 可以组成一个a,在我看来下限是|B| ≥ log2(|A| + 1),因为我们最多可以有@ 987654329@B 的非空子集。
【解决方案2】:

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 也为真时才为真。有一个关于编码的有用文档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)

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

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| &lt; |A| 时才认为解决方案有效,但我很想在外面这样做

【讨论】:

    猜你喜欢
    • 2020-09-18
    • 1970-01-01
    • 2018-12-20
    • 2011-12-17
    • 2017-11-28
    • 2022-01-15
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多