【问题标题】:Removing duplicate solutions删除重复的解决方案
【发布时间】:2012-11-15 08:33:32
【问题描述】:

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

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]].

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

【问题讨论】:

    标签: list prolog prolog-cut


    【解决方案1】:

    已经有 3 个 SO-answers,但我不能同意一个!它们都是错误的。

    如何消除冗余解决方案

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

    合并L([],[],[])。 合并(列表,[],列表)。 合并([],列表,列表)。

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

    合并L([],[],[])。 合并(列表,[],列表):- 列表 = [_|_]。 合并([],列表,列表):- 列表 = [_|_]。

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

    如何消除剩余的选择点

    尽管如此,使用削减还是有一些意义的:他们能够删除一个选择点。但是这样的切口需要像这样适当地保护:

    合并L(Xs,Ys,Zs):- ( Xs == [], Ys == [] -> ! ; Zs == [] -> ! ;真的 ), Xs = [],Ys = [],Zs = []。 ...

    我不确定这是否值得努力...实现可能会更有效地提供这一点。有关这方面的更多详细信息,请参阅 this 和 this。

    尾递归

    对您来说更有趣的可能是最后一条规则的更改。它应该改为:

    合并L([X|Rest],[Y|Rest2],[XY|Res2]):- 附加(X,Y,XY), 合并L(休息,休息2,休息2)。

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

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

    原版:

    ?- N 是 2^20,长度(L,N),maplist(=([]),L),时间(mergeL(L,L,J))。 % 2,097,206 次推理,5.232 CPU 在 5.250 秒内(100% CPU,400851 唇)

    尾递归版本:

    ?- N 是 2^20,长度(L,N),maplist(=([]),L),时间(mergeL(L,L,J))。 % 2,097,152 推理,0.525 CPU 在 0.526 秒内(100% CPU,3997337 唇)

    Th-那是 10 倍。

    现在有了更长的列表: 尾递归版本:

    ?- N 是 2^22,长度(L,N),maplist(=([]),L),时间(mergeL(L,L,J))。 % 8,388,608 推理,4.228 CPU 在 4.237 秒内(100% CPU,1984272 唇)

    与原来的顺序:

    ?- N 是 2^22,长度(L,N),maplist(=([]),L),时间(mergeL(L,L,J))。 % 1,765,930 次推理,1.029 CPU 在 1.033 秒内(100% CPU,1716119 唇) 错误:超出本地堆栈

    所以只执行了 1.7Mi 来填满本地堆栈。这主要是空间问题!如果你的内存比我多,直接增加N is 2^22!

    【讨论】:

    • 感谢您非常详尽的回答。 :) 一个问题:在 Tail recursiveness 部分中,您建议切换追加和合并的顺序,对吗?我不知道顺序很重要!
    • @Enoon:你应该切换它们,绝对!递归调用必须是最后一次。您可以笼统地说:如果我们有两个确定的目标,则将递归的目标放在最后。前提是这不会影响终止(在这种情况下不会)。
    【解决方案2】:

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

    如果你在第一个子句的正文中放置一个切口,你应该没问题:

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

    【讨论】:

    • 谢谢。所以基本上删减是说“不再使用这个子句”的一种方式?
    • 它说“没有其他选择可以尝试”。
    【解决方案3】:

    我会做出最后的选择:

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

    @twinterer 答案也有效!

    【讨论】:

      【解决方案4】:

      您可以添加剪辑:

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

      【讨论】:

        猜你喜欢
        • 2017-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-19
        • 2013-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多