如何从购物清单的多个捆绑包中找到最便宜的产品组合
How to find the cheapeast combination of products to buy from multiple bundles for a shopping list
假设我有不同的产品组合,每个产品组合都有一个价格。
Name Price Products
Fruit Overdose 5$ 2 Apples, 1 Orange, 1 Banana
Doctors Darling 1$ 1 Apple
The Exotic 3.50$ 2 Oranges, 1 Banana
Vitamin C 1.5$ 1 Orange
我有一个购物清单,例如我要购买:
4 Apples, 1 Orange, 2 Bananas.
问题
我如何找到最便宜的捆绑包组合来购买给定的购物清单? 根据购物清单 购买超过要求 是有效的。
我只需要一个与语言无关的提示,告诉我如何最有效地解决这个问题。
我的现实世界问题有点复杂,还包括我已经拥有的产品列表;但这应该不是很重要。
这是一个 dual linear programming 问题:
- 你要最小化的objective就是总价,是一个线性函数
- 约束可以表示为矩阵方程,其中每一行对应一个捆绑包,每一列对应一种产品。
你可以通过constructing the dual which will be a standard linear programming problem, applying a standard algorithm such as the simplex algorithm来解决它,然后将解决方案转换回原始对偶问题的解决方案。
这种问题用SAT/SMT solver. Z3 is an open source solver with bindings for many languages, including a nicely integrated binding with Python就可以轻松解决了。
在求解器中,您只需声明几个变量(4 个用于每个捆绑包的重量,1 个用于总价)。然后你写下不同的约束。在这种情况下,约束是:
- 权重应该是正数。
- 总价的计算方法是将价格乘以每捆重量。
- 对于每种水果,应购买所需的最少数量。
请注意,我以美分计算价格,以便能够使用整数,尽管这对于 Z3 来说并不是绝对必要的。
Python 代码如下所示:
from z3 import *
bundles = [["Fruit Overdose", 5, {'apple': 2, 'orange': 1, 'banana': 1}],
["Doctors Darling", 1, {'apple': 1}],
["The Exotic", 3.50, {'orange': 2, 'banana': 1}],
["Vitamin C", 1.5, {'orange': 1}]]
desired = {'apple': 4, 'orange': 1, 'banana': 2}
num_bundles = len(bundles)
W = [Int(f'W_{i}') for i in range(len(bundles))] # weight of each bundle: how many to buy of each bundle
TotalPrice = Int('Total')
s = Optimize()
s.add(TotalPrice == Sum([W[i] * int(b[1] * 100) for i, b in enumerate(bundles)]))
s.add([W[i] >= 0 for i in range(len(W))]) # weights can not be negative
for f in desired:
s.add(Sum([W[i] * b[2][f] for i, b in enumerate(bundles) if f in b[2]]) >= desired[f])
h1 = s.minimize(TotalPrice)
result = s.check()
print("optimizer result:", result)
if result == sat:
s.lower(h1)
m = s.model()
print(f"The lowest price is: {m[TotalPrice].as_long()/100:.2f}")
for i,b in enumerate(bundles):
print(f" Buying {m[W[i]].as_long()} of {b[0]}")
输出:
The lowest price is: 10.00
Buying 2 of Fruit Overdose
Buying 0 of Doctors Darling
Buying 0 of The Exotic
Buying 0 of Vitamin C
如果您只是将 Fruit Overdose 的价格更改为 6,结果将是:
Buying 0 of Fruit Overdose
Buying 4 of Doctors Darling
Buying 2 of The Exotic
Buying 0 of Vitamin C
算法保证找到最佳解决方案。如果有多个同样好的解决方案,则只返回其中一个。
假设我有不同的产品组合,每个产品组合都有一个价格。
Name Price Products
Fruit Overdose 5$ 2 Apples, 1 Orange, 1 Banana
Doctors Darling 1$ 1 Apple
The Exotic 3.50$ 2 Oranges, 1 Banana
Vitamin C 1.5$ 1 Orange
我有一个购物清单,例如我要购买:
4 Apples, 1 Orange, 2 Bananas.
问题
我如何找到最便宜的捆绑包组合来购买给定的购物清单? 根据购物清单 购买超过要求 是有效的。
我只需要一个与语言无关的提示,告诉我如何最有效地解决这个问题。 我的现实世界问题有点复杂,还包括我已经拥有的产品列表;但这应该不是很重要。
这是一个 dual linear programming 问题:
- 你要最小化的objective就是总价,是一个线性函数
- 约束可以表示为矩阵方程,其中每一行对应一个捆绑包,每一列对应一种产品。
你可以通过constructing the dual which will be a standard linear programming problem, applying a standard algorithm such as the simplex algorithm来解决它,然后将解决方案转换回原始对偶问题的解决方案。
这种问题用SAT/SMT solver. Z3 is an open source solver with bindings for many languages, including a nicely integrated binding with Python就可以轻松解决了。
在求解器中,您只需声明几个变量(4 个用于每个捆绑包的重量,1 个用于总价)。然后你写下不同的约束。在这种情况下,约束是:
- 权重应该是正数。
- 总价的计算方法是将价格乘以每捆重量。
- 对于每种水果,应购买所需的最少数量。
请注意,我以美分计算价格,以便能够使用整数,尽管这对于 Z3 来说并不是绝对必要的。
Python 代码如下所示:
from z3 import *
bundles = [["Fruit Overdose", 5, {'apple': 2, 'orange': 1, 'banana': 1}],
["Doctors Darling", 1, {'apple': 1}],
["The Exotic", 3.50, {'orange': 2, 'banana': 1}],
["Vitamin C", 1.5, {'orange': 1}]]
desired = {'apple': 4, 'orange': 1, 'banana': 2}
num_bundles = len(bundles)
W = [Int(f'W_{i}') for i in range(len(bundles))] # weight of each bundle: how many to buy of each bundle
TotalPrice = Int('Total')
s = Optimize()
s.add(TotalPrice == Sum([W[i] * int(b[1] * 100) for i, b in enumerate(bundles)]))
s.add([W[i] >= 0 for i in range(len(W))]) # weights can not be negative
for f in desired:
s.add(Sum([W[i] * b[2][f] for i, b in enumerate(bundles) if f in b[2]]) >= desired[f])
h1 = s.minimize(TotalPrice)
result = s.check()
print("optimizer result:", result)
if result == sat:
s.lower(h1)
m = s.model()
print(f"The lowest price is: {m[TotalPrice].as_long()/100:.2f}")
for i,b in enumerate(bundles):
print(f" Buying {m[W[i]].as_long()} of {b[0]}")
输出:
The lowest price is: 10.00
Buying 2 of Fruit Overdose
Buying 0 of Doctors Darling
Buying 0 of The Exotic
Buying 0 of Vitamin C
如果您只是将 Fruit Overdose 的价格更改为 6,结果将是:
Buying 0 of Fruit Overdose
Buying 4 of Doctors Darling
Buying 2 of The Exotic
Buying 0 of Vitamin C
算法保证找到最佳解决方案。如果有多个同样好的解决方案,则只返回其中一个。