NP完全背包
我看到 这个 ECLiPSe 解决了 此 XKCD漫画。我尝试将其转换为纯 Prolog。
go:-
Total = 1505,
Prices = [215, 275, 335, 355, 420, 580],
length(Prices, N),
length(Amounts, N),
totalCost(Prices, Amounts, 0, Total),
writeln(Total).
totalCost([], [], TotalSoFar, TotalSoFar).
totalCost([P|Prices], [A|Amounts], TotalSoFar, EndTotal):-
between(0, 10, A),
Cost is P*A,
TotalSoFar1 is TotalSoFar + Cost,
totalCost(Prices, Amounts, TotalSoFar1, EndTotal).
我不认为这是人们能想到的最好/最具声明性的解决方案。有人有任何改进建议吗?提前致谢!
I saw this ECLiPSe solution to the problem mentioned in this XKCD comic. I tried to convert this to pure Prolog.
go:-
Total = 1505,
Prices = [215, 275, 335, 355, 420, 580],
length(Prices, N),
length(Amounts, N),
totalCost(Prices, Amounts, 0, Total),
writeln(Total).
totalCost([], [], TotalSoFar, TotalSoFar).
totalCost([P|Prices], [A|Amounts], TotalSoFar, EndTotal):-
between(0, 10, A),
Cost is P*A,
TotalSoFar1 is TotalSoFar + Cost,
totalCost(Prices, Amounts, TotalSoFar1, EndTotal).
I don't think that this is the best / most declarative solution that one can come up with. Does anyone have any suggestions for improvement? Thanks in advance!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
既然你提到了 SWI-Prolog 为什么不
和
library( lambda)
通过说明这一点,列表
Amounts
中的所有变量都在有限范围内。因此,无需对上限进行“数学计算”(无论如何,这经常会出错)。要查看具体的解决方案,需要labeling/2:
Since you mention SWI-Prolog why not
and
library(lambda)
By stating this, all variables in the list
Amounts
are in a finite range. So there is no need to "do the math" for an upper bound (which often goes wrong anyway).To see concrete solutions, labeling/2 is needed:
任何具有几天以上经验的 Prolog 程序员都应该能够理解您的生成和测试方法。这里有一些小的调整:
我将 go/0 更改为 go/1,以便 Prolog 引擎回溯并生成所有解决方案(有两个)。可以省略对 length/2 的调用,因为 totalCost/4 负责构建列表 Amounts 使其与 Prices 具有相同的长度。我使用了 write/1 和 nl/0 来使其更加便携。
在 totalCost/4 中,我缩短了一些变量/参数名称,并为累加器参数使用了一个有点搞笑的名称。我检查累加器不超过所需 Total 的方式使用了对 Between/3 的原始调用,但使用了计算出的上限而不是常量。在我的机器上,它将运行时间从几分钟缩短到几秒钟。
补充:我应该在这里提到我在上面的评论中所说的内容,菜单项现在按从最贵到最便宜的顺序排列。使用 SWI-Prolog 的 time/1 谓词表明,这将工作量从 1,923 次推理减少到 1,070 次推理。主要的改进(速度)来自于对 A 使用计算边界,而不是对每个项目使用 0 到 10 的范围。
请注意复合目标周围的额外括号,否则 SWI-Prolog 认为我们正在调用未定义的 time/2 谓词。
Your generate-and-test approach should be intelligible to any Prolog programmer with more than a few days experience. Here are some minor tweaks:
I changed go/0 to go/1 so that the Prolog engine will backtrack and produce all the solutions (there are two). The calls to length/2 could be omitted because totalCost/4 does the work of building list Amounts to have equal length with Prices. I used write/1 and nl/0 to make it a little more portable.
In totalCost/4 I shortened some of the variable/argument names and indulged in a slightly jokey name for the accumulator argument. The way I imposed the check that our accumulator doesn't exceed the desired Total uses your original call to between/3 but with a computed upper bound instead of a constant. On my machine it reduced the running time from minutes to seconds.
Added: I should mention here what was said in my comment above, that the menu items are now ordered from most expensive to least. Using SWI-Prolog's time/1 predicate shows this reduces the work from 1,923 inferences to 1,070 inferences. The main improvement (in speed) comes from using computed bounds on A rather than range 0 to 10 for every item.
Note the extra parentheses around the compound goal, as otherwise SWI-Prolog thinks we are calling an undefined time/2 predicate.
可以简单地用 clpBNR 表达:
swi-prolog 中的结果:
Can express simply in clpBNR:
Result in swi-prolog: