【问题标题】:How to prevent Prolog from backtracking where it shouldn't如何防止Prolog回溯到不应该的地方
【发布时间】:2014-03-23 16:32:37
【问题描述】:

我正在尝试解决一个 CSP,我需要将鸡尾酒分发给调酒师,以便每个调酒师最多有一种鸡尾酒,并且所有鸡尾酒都有一个调酒师。我通过创建一个 clpfd 变量列表来解决它,首先给他们所有调酒师的完整域,然后删除所有不知道如何制作鸡尾酒的调酒师。
我的代码有效,但有一个问题:它太慢了。如果我查看分析器,remove_domain 会被调用 2000 次(对于我给程序的输入),而它的重做统计量是 >100 000。 我需要在这些功能之一(或两者)中进行哪些更改,以便 prolog 不需要回溯?

produce_domains(_,_,[],[]) :- !.
produce_domains(Bartenders,NBartenders,[Cocktail|Cocktails],[Var|Vars]) :-
    Var in 1..NBartenders,
    remove_domain(Bartenders,NBartenders,Cocktail,Var),!,
    produce_domains(Bartenders,NBartenders,Cocktails,Vars),!.

remove_domain([],0,_,_) :- !.
remove_domain([Bartender|Bartenders],NBartenders,Cocktail,Var) :-
    (\+ member(Cocktail,Bartender) -> Var #\= NBartenders;!),!,
    NNBartenders is NBartenders - 1,
    remove_domain(Bartenders,NNBartenders,Cocktail,Var),!.

我已经阅读了this related question,但我使用的是最新的 Windows 版本的 SWI-Prolog(5.10.5),所以这不应该是这里的问题。

【问题讨论】:

  • 切割不能与 CLP(FD) 很好地混合。而是尝试重新表述您的问题没有成员、\+等...(也许 all_different 会有所帮助)

标签: prolog clpfd


【解决方案1】:

你不需要这么多!/0:Prolog 经常可以看出你的谓词是确定性的。

首先让我提供以下版本的代码。它使用更相关的名称,不包含!/0,并使用高阶谓词使代码更短。

:- use_module(library(clpfd)).

bartenders_cocktails_variables(Bs, Cs, Vs) :-
        length(Bs, LBs),
        maplist(bartenders_cocktail_variable(Bs, LBs), Cs, Vs).

bartenders_cocktail_variable(Bs, N, C, V) :-
        V in 1..N,
        foldl(compatible_bartender(C,V), Bs, 1, _).

compatible_bartender(C, V, Cs, N0, N1) :-
        (   member(C, Cs) -> true
        ;   V #\= N0
        ),
        N1 #= N0 + 1.

请注意,我是在向上而不是向下计数来列举调酒师(这只是他们能够混合的鸡尾酒的列表),因为这看起来更自然。我还可以通过简单地切换 if-then-else 的分支来省略 (\+)/1

示例查询,表明在此用例中谓词是确定性的:

?- bartenders_cocktails_variables([[a,b],[a,b],[x,y]], [x,a,b], Vars).
Vars = [3, _G1098, _G1101],
_G1098 in 1..2,
_G1101 in 1..2.

我们看到:鸡尾酒x必须由第三个调酒师等混合。

我认为您程序的这一部分可能对您所描述的缓慢性能负责。也许您程序的其他部分(无意中)不是确定性的?也许尝试不同的标签策略或其他限制?如果您发布更多上下文,我们可能会为您提供更多帮助。

【讨论】:

  • 有一个小问题:swi-prolog 5.10.5 中不存在 foldl...所以现在我注意到我对我使用的是最新的 Windows 版本的评论不正确,但我自己在那里放了一个 foldl 的实现。现在 bartenders_cocktails_variables 的调用次数正好合适(每个订单一次),但它需要 60% 的计算时间。其他 40% 在 clfpd:all_distinct 中。这 60% 中的 39 个是对 clfpd:#\=/2 的调用。至于规模: compatible_bartender 在分析器中有 270 万次调用和 410 万次重做。
  • 如果还没有,请考虑仅在声明兼容性约束之后(即 bartenders_cocktails_variables/3)发布all_distinct/1 ,从那时起更多的事情已经知道,all_distinct/1 需要做的工作更少。此外,您可以考虑建立一个域(如1\/3\/7..9)并为每个变量只发布一个in/2 约束,而不是#\=/2 约束,而不是发布一个in/2 约束和几个不等式。您可以轻松地调整compatible_bartender/5 为每种鸡尾酒构建一个合适的域。
  • 整个程序中确实只有一个all_distinct,所以没错。我曾尝试建立域,但例如这不起作用:
  • 好的,我之前的评论太快了:我确实建立了域而不是分解它,现在程序稍微快了一点。但仍然不够快完成任务。
猜你喜欢
  • 1970-01-01
  • 2018-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多