Python中总物品限制的背包问题
Posted
技术标签:
【中文标题】Python中总物品限制的背包问题【英文标题】:Knapsack problem with a total item limit in Python 【发布时间】:2021-09-27 22:03:16 【问题描述】:我正在尝试用 Python 解决一个有额外要求的背包问题。我找到了很多背包代码和数学解释,但我找不到任何完全适合我正在尝试做的事情。老实说,数学信息超出了我的想象,所以这就是我在这里问的原因。我很乐意学习使用任何可用的库,只要它是免费的。 Numpy、pandas、ortools等
假设我有 20 美元,我想购买 6 瓶单独的啤酒,用于混合搭配的六件装容器。每种啤酒都有名称、类型、价格(类似于背包的“重量”)和评分(类似于背包的“价值”)。 我想购买评分最高的组合,同时遵守以下规则:
总共花费不到 20 美元 正好买 6 瓶啤酒 我还想确保我总是得到 2 个 Lager、1 个 Stout 和 1 个 Amber。其他 2 种啤酒可以是任何类型,但所有 6 种啤酒都必须是独一无二的。这只是一个小数据列表作为示例。我知道蛮力可能在这里起作用,但我的真实数据要大得多,而且我的第一次尝试(在发现这是一个常见问题之前)从未完成蛮力,并且需要方式太长时间才能找到解决方案.所以我正在寻找比蛮力更好的东西。
capacity = 6
money = 20
beers = [
"name":"Beer1", "type":"Lager", "price":3.50, "score":4.1,
"name":"Beer2", "type":"Porter", "price":4.90, "score":4.5,
"name":"Beer3", "type":"IPA", "price":3.70, "score":4.0,
"name":"Beer4", "type":"Stout", "price":3.20, "score":4.2,
"name":"Beer5", "type":"Amber", "price":3.80, "score":3.9,
"name":"Beer6", "type":"Stout", "price":2.70, "score":2.9,
"name":"Beer7", "type":"IPA", "price":2.50, "score":3.2,
"name":"Beer8", "type":"Pilsner", "price":3.10, "score":4.0,
"name":"Beer9", "type":"Amber", "price":3.00, "score":4.1,
"name":"Beer10", "type":"Porter", "price":2.80, "score":3.3,
"name":"Beer11", "type":"IPA", "price":3.70, "score":4.0,
"name":"Beer12", "type":"Lager", "price":3.20, "score":4.2,
"name":"Beer13", "type":"Amber", "price":3.30, "score":3.5,
"name":"Beer14", "type":"Stout", "price":2.90, "score":2.8,
"name":"Beer15", "type":"Lager", "price":3.20, "score":4.2,
]
所需的输出将是为解决方案选择的名称列表,如下所示:["Beer12","Beer14","Beer13","Beer1","Beer7","Beer6"]
感谢收看。我希望我们能找到一个解决方案,对我和其他任何试图解决未来类似问题的人来说。
【问题讨论】:
or-tools 提供您需要的一切。浏览他们的文档,决定要使用的核心求解器(cp-sat 与混合整数编程包装器,而不是背包求解器;前者 = cp-sat 更容易使用恕我直言 -> 但这个只是离散域=> 你需要将你的价格/分数乘以 integral) 并尝试实现它。背包示例应该足以推断您的附加限制(带有一些抽象思维)。遇到问题时,您可能会用更具体的内容更新您的问题。 你可能想看看one of the knapsack examples, here: python + cp-sat。 暴力破解实际上是解决这些问题的唯一途径。问题是NP完全的。你只需要聪明地使用蛮力。 @TimRoberts 从理论上讲,是的......但如果工业界对他们遇到的任何 NP 难题使用蛮力,那么商业 MIP 求解器将没有市场(它们非常昂贵且仍在购买)。在这种情况下,整数编程的问题非常自然(是的...我的选择偏爱一种有点偏离纯 IP 的混合),以至于很难击败开源像 CoinOR Cbc 之类的求解器(带有一个简单的模型),也可能是 or-tools cp-sat:可能甚至数周或数月的工作都不够。 【参考方案1】:它可能类似于以下示例。
我们:
使用 cp-sat 作为求解器 缩放价格和分数以便能够使用积分域 cp-sat 需要这个可能包含一个错误,因为我没有检查太多,但无论如何它更多的是关于一般概念。它还表示求解器的建模能力。
代码
from ortools.sat.python import cp_model
# DATA
capacity = 6
money = 20
beers = [
"name":"Beer1", "type":"Lager", "price":3.50, "score":4.1,
"name":"Beer2", "type":"Porter", "price":4.90, "score":4.5,
"name":"Beer3", "type":"IPA", "price":3.70, "score":4.0,
"name":"Beer4", "type":"Stout", "price":3.20, "score":4.2,
"name":"Beer5", "type":"Amber", "price":3.80, "score":3.9,
"name":"Beer6", "type":"Stout", "price":2.70, "score":2.9,
"name":"Beer7", "type":"IPA", "price":2.50, "score":3.2,
"name":"Beer8", "type":"Pilsner", "price":3.10, "score":4.0,
"name":"Beer9", "type":"Amber", "price":3.00, "score":4.1,
"name":"Beer10", "type":"Porter", "price":2.80, "score":3.3,
"name":"Beer11", "type":"IPA", "price":3.70, "score":4.0,
"name":"Beer12", "type":"Lager", "price":3.20, "score":4.2,
"name":"Beer13", "type":"Amber", "price":3.30, "score":3.5,
"name":"Beer14", "type":"Stout", "price":2.90, "score":2.8,
"name":"Beer15", "type":"Lager", "price":3.20, "score":4.2,]
# PREPROCESSING
n_beers = len(set([entry['name'] for entry in beers]))
# MODEL
model = cp_model.CpModel()
x_select = [model.NewBoolVar('') for i in range(n_beers)]
# select exactly "capacity"
model.Add(sum(x_select) == capacity)
# spend not too much -> # ASSUMPTION: * 100 makes all the values integral
model.Add(sum([x_select[i] * int(round(beers[i]['price']*100)) for i in range(n_beers)]) <= money * 100)
# >= 2 lagers needed
model.Add(sum([x_select[i] for i in range(n_beers) if beers[i]['type'] == 'Lager']) >= 2)
# >= 1 Stout needed
model.Add(sum([x_select[i] for i in range(n_beers) if beers[i]['type'] == 'Stout']) >= 1)
# >= 1 Amber needed
model.Add(sum([x_select[i] for i in range(n_beers) if beers[i]['type'] == 'Amber']) >= 1)
# maximize sum of scores selected -> # ASSUMPTION: * 10 makes all the values integral
model.Maximize(sum([x_select[i] * int(round(beers[i]['score']*10)) for i in range(n_beers)]))
# SOLVE
solver = cp_model.CpSolver()
solver.parameters.log_search_progress = True
model.Proto().objective.scaling_factor = -1./10 # inverse scaling for solver logging output
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
selected = [i for i in range(n_beers) if solver.Value(x_select[i]) == 1]
print("\n".join([str(beers[i]) for i in selected]))
修整后的输出
status: OPTIMAL
objective: 24.8
best_bound: 24.8
booleans: 8
conflicts: 0
branches: 19
propagations: 20
integer_propagations: 42
restarts: 17
lp_iterations: 4
walltime: 0.0689822
usertime: 0.0689823
deterministic_time: 2.03413e-05
primal_integral: 1.69201e-05
Total cuts added: 3 (out of 4 calls) worker: ''
- num simplifications: 0
- added 1 cut of type 'CG'.
- added 1 cut of type 'MIR_1'.
- added 1 cut of type 'MIR_2'.
'name': 'Beer1', 'type': 'Lager', 'price': 3.5, 'score': 4.1
'name': 'Beer4', 'type': 'Stout', 'price': 3.2, 'score': 4.2
'name': 'Beer8', 'type': 'Pilsner', 'price': 3.1, 'score': 4.0
'name': 'Beer9', 'type': 'Amber', 'price': 3.0, 'score': 4.1
'name': 'Beer12', 'type': 'Lager', 'price': 3.2, 'score': 4.2
'name': 'Beer15', 'type': 'Lager', 'price': 3.2, 'score': 4.2
【讨论】:
这太棒了——效果很好。如果需要,我想我可以弄清楚如何使用您链接的文档进行调整。谢谢!以上是关于Python中总物品限制的背包问题的主要内容,如果未能解决你的问题,请参考以下文章