删除重复的解决方

时间:2012-11-15 08:33:33

标签: list prolog prolog-cut

我的代码按以下方式逐项合并两个列表列表:

mergeL([[a,b],[c,d]], [[1,2],[3,4]], Result). Result = [[a,b,1,2],[c,d,3,4]]

这是我使用的代码:

mergeL([],[],[]).
mergeL(List, [], List).
mergeL([], List, List).
mergeL([X|Rest],[Y|Rest2], [XY|Res2]) :-
   mergeL(Rest, Rest2, Res2),
   append(X,Y,XY).

这似乎有效,但如果我用两个相同大小的列表调用它,我会得到三个重复的结果。示例(两个列表只包含一个元素):

?- mergeL([[a,b]],[[1,2,3]],Q).
Q = [[a, b, 1, 2, 3]] ;
Q = [[a, b, 1, 2, 3]] ;
Q = [[a, b, 1, 2, 3]].

是否有一种干净的方法可以使这个输出只有一个解决方案?

4 个答案:

答案 0 :(得分:3)

已经有3个SO答案,但我不能同意一个答案!它们都是不正确的。

如何消除冗余解决方案

考虑你定义中的三个事实:

mergeL([],[],[]).
mergeL(List, [], List).
mergeL([], List, List).

他们都成功mergeL([],[],[]),这是你裁员的来源。第二个和第三个事实是List是一个非空列表。所以让我们将其添加到定义中:

mergeL([],[],[]).
mergeL(List, [], List) :- List = [_|_].
mergeL([], List, List) :- List = [_|_].

这消除了冗余解决方案。无需切割即可删除冗余解决方案。然而,other SO-answer中引入的削减可能会隐藏解决方案。对于查询mergeL(Xs,YS,Zs),切割版本只有一个解决方案,但应该有无限多个。

如何消除剩余的选择点

然而,使用剪辑有一些意义:他们能够删除一个选择点。但是这样的削减需要得到适当的保护:

mergeL(Xs, Ys, Zs) :-
   ( Xs == [], Ys == [] -> !
   ; Zs == [] -> !
   ; true
   ),
   Xs = [], Ys = [], Zs = [].
...

我不确定这是否值得付出努力......实施可能会更有效地提供此功能。有关详细信息,请参阅 thisthis

尾递归

对你来说更有趣的可能是最后一条规则的改变。它应该读取:

mergeL([X|Rest],[Y|Rest2], [XY|Res2]) :-
   append(X,Y,XY),
   mergeL(Rest, Rest2, Res2).

这可以避免临时分配本地堆栈空间。所以这绝对是一种优化。但是优化不会损害谓词的逻辑读取。

在我的2009年32位笔记本电脑(几乎是蒸汽驱动)和SWI 6.3.3-40-g064f37b:

原始版本:

?- N is 2^20, length(L,N), maplist(=([]),L), time(mergeL(L,L,J)).
% 2,097,206 inferences, 5.232 CPU in 5.250 seconds (100% CPU, 400851 Lips)

尾递归版:

?- N is 2^20, length(L,N), maplist(=([]),L), time(mergeL(L,L,J)).
% 2,097,152 inferences, 0.525 CPU in 0.526 seconds (100% CPU, 3997337 Lips)

这是10倍。

现在有更长的名单: 尾递归版:

?- N is 2^22, length(L,N), maplist(=([]),L), time(mergeL(L,L,J)).
% 8,388,608 inferences, 4.228 CPU in 4.237 seconds (100% CPU, 1984272 Lips)

原始订单:

?- N is 2^22, length(L,N), maplist(=([]),L), time(mergeL(L,L,J)).
% 1,765,930 inferences, 1.029 CPU in 1.033 seconds (100% CPU, 1716119 Lips)
ERROR: Out of local stack

因此只执行1.7Mi来填充本地堆栈。这主要是空间问题!如果你有比我更多的记忆,只需增加N is 2^22

答案 1 :(得分:1)

您可以添加剪辑:

mergeL([],[],[]) :- !.
mergeL(List, [], List):- !.
mergeL([], List, List):- !.
mergeL([X|Rest],[Y|Rest2], [XY|Res2]) :-
   mergeL(Rest, Rest2, Res2),
   append(X,Y,XY).

答案 2 :(得分:1)

第一个列表为空的查询,即?-merge([],)将匹配第一个和第三个子句。同样,第二个列表为空的查询将匹配第一个和第二个子句。这就是为什么你得到选择点,因此重复解决方案。

如果你在第一个条款的正文中放置一个剪辑,你应该没问题:

merge([],[],[]) :- !.

答案 3 :(得分:1)

我会做出最后的选择:

mergeL([X|Rest],[Y|Rest2], [XY|Res2]) :-
    mergeL(Rest, Rest2, Res2), !, append(X,Y,XY).

也@twinterer回答有效!