如何防止Prolog在不应该回溯的地方进行回溯

时间:2014-03-23 16:32:37

标签: prolog clpfd

我正在尝试解决一个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,但我使用的是SWI-Prolog(5.10.5)的最新Windows版本,所以这不应该是问题。

1 个答案:

答案 0 :(得分:2)

您不需要这么多!/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必须由第三个调酒师等混合。

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