【问题标题】:Strange results for list manipulation列表操作的奇怪结果
【发布时间】:2014-10-25 08:44:17
【问题描述】:

我正在尝试在 Prolog 中实现一些用于列表操作的谓词。 一切都按预期工作。例如

append([],Ys,Ys).
append([X|Xs],Ys,[X|Zs]) :- append(Xs,Ys,Zs). 

示例查询:

?- append([1,2,3],[4,5,6],X).
X = [1,2,3,4,5,6].                   % OK

但我在使用 'delete' 谓词时遇到了问题。以下是它的实现方式:

delete(X,[Y|Ys],Zs) :- X == Y, delete(X,Ys,Zs).
delete(X,[_X|Ys],[_Z|Zs]) :- delete(X,Ys,Zs).
delete(_X,[],[]).

错误结果的示例查询:

?- delete(4,[1,2,3,4],X).
X = [_G8975, _G8978, _G8981].       % BAD

我已经通过进一步的输入对其进行了测试,它总是返回一个预期长度的列表,因此它以某种方式工作。但是为什么我只得到那些神秘的 _GXXXX 而不是数字呢?

非常感谢!

【问题讨论】:

    标签: prolog prolog-dif


    【解决方案1】:

    您的程序中有几个问题。但首先,作为初学者:

    坚持 Prolog 的纯单调子集

    在您的程序中,您使用的 (==)/2 不再属于该纯子集。在您的情况下,将其替换为 (=)/2

    始终查看所有答案

    Prolog 的顶级循环只显示第一个答案。您必须通过按 ;SPACE 来要求更多。您的带有(=)/2 的程序给出(具有更多可读变量):

    ?- delete(4,[1,2,3,4],X).
    X = [_A,_B,_C] ;
    X = [_A,_B,_C,_D] ;
    false.
    

    也就是说:不仅第一个答案出乎意料,而且还有第二个答案,列表长度与原始答案相同。另一方面,第一个答案包括预期的解决方案。所以程序比较笼统。

    减小输入大小

    ?- delete(4,[4],L).
    L = [] ;
    L = [_A] ;
    false.
    

    第一个答案现在看来是正确的,但第二个答案完全出乎意料。也就是说,定义过于笼统,正如delete(4,[4],[any])所见证的那样

    专业化程序

    要本地化程序,请通过尽可能多地引入 false= 等目标来专门化程序,并且只要 delete(4,[4],[any]) 成功.我想出:

    ?- 删除(4,[4],[任何])。 delete(X,[Y|Ys],Zs) :- false, X = Y, delete(X,Ys,Zs)。 删除(X,[_X|Ys],[_Z|Zs]) :- X = 4, _X = 4, _Z = 任意, 删除(X,Ys,Zs)。 删除(_X,[],[]):- _X = 4。

    现在应该很明显,在这条规则中,_X =4, _Z = any 应该是相同的,而X = 4, _X = 4 应该是不同的。不等式最好用dif/2 表达。

    delete(_X, [], []).
    delete(X, [Y|Ys], [Y|Zs]) :-
       dif(X, Y),
       delete(X, Ys, Zs).
    delete(X, [X|Ys], Zs) :-
       delete(X, Ys, Zs).
    

    这个定义现在可以用在很多方面。喜欢

    ?- delete(X,Xs,[1,2,3]).
    Xs = [1, 2, 3],
    dif(X, 3),
    dif(X, 2),
    dif(X, 1) ;
    Xs = [1, 2, 3, X],
    dif(X, 3),
    dif(X, 2),
    dif(X, 1) ;
    Xs = [1, 2, 3, X, X],
    dif(X, 3),
    dif(X, 2),
    dif(X, 1) ...
    

    请注意,现在有无限多的答案!

    【讨论】:

    • 非常感谢您的解释!我总是对提示和最佳实践感兴趣...
    【解决方案2】:

    这个答案的灵感来自 logically-pure 代码 @false 呈现in his answer

    让我们使用元谓词tfilter/3 并具体化术语不等式dif/3 并简单地写:

    ?- Vs0 = [1,2,3,4], tfilter(dif(4),Vs0,Vs)。 Vs0 = [1,2,3,4], VS = [1,2,3]。 % 确定性地成功 ?- Vs0 = [1,2,3,4,2,3,4], tfilter(dif(2),Vs0,Vs)。 Vs0 = [1,2,3,4,2,3,4], Vs = [1, 3,4, 3,4]。 % 确定性地成功

    delete/3的实现归结为:

    delete(E,Vs0,Vs) :-
       tfilter(dif(E),Vs0,Vs).
    

    就像@false 的代码一样,这个实现是monotone,它使谓词具有多种用途,使您能够获得逻辑上合理的答案 即使在使用非地面术语时也是如此。

    最后,让我们来一个非常笼统的查询,看看所有的答案:

    ?- Vs0 = [X,Y,Z], 删除(E,Vs0,Vs)。 Vs0 = [X,Y,Z], E=X , E=Y , E=Z , Vs = [ ] ; Vs0 = [X,Y,Z], E=X , E=Y , dif(E,Z), Vs = [ Z] ; Vs0 = [X,Y,Z], E=X , 差异(E,Y), E=Z , Vs = [ Y ] ; Vs0 = [X,Y,Z], E=X , 差异(E,Y), 差异(E,Z), Vs = [ Y,Z] ; Vs0 = [X,Y,Z],dif(E,X ), E=Y , E=Z , Vs = [X ] ; Vs0 = [X,Y,Z], dif(E,X ), E=Y , dif(E,Z), Vs = [X, Z] ; Vs0 = [X,Y,Z],dif(E,X ), dif(E,Y), E=Z , Vs = [X,Y ] ; Vs0 = [X,Y,Z], dif(E,X ), dif(E,Y), dif(E,Z), Vs = [ X,Y,Z]。

    【讨论】:

      【解决方案3】:

      去除隐晦变量名的一种隐秘方法是使用numbervars/3,例如,

      ?- length(X, 3).
      X = [_G2019, _G2022, _G2025].
      
      ?- length(X, 3), numbervars(X, 0, _).
      X = [A, B, C].
      

      现在联系您的delete/3。除了其他小问题,例如参数的异常顺序、子句的异常顺序等,您还有一个主要问题:在第二个子句中,您将一个新的匿名变量放在原始列表的元素应该在的位置。

      delete(X, [Y|Ys], [Y|Zs]) :- delete(X, Ys, Zs).
      

      在第二个子句中有这个,它有点工作:

      ?- delete(4, [1,2,3,4], L).
      L = [1, 2, 3] .
      
      ?- delete(2, [1,2,3,4,2,3,4], L).
      L = [1, 3, 4, 3, 4] .
      

      您的实施还存在其他问题。您可以查看来自 SWI-Prolog 的 library(lists) 中相同谓词的 implementation:也请阅读文档!

      【讨论】:

      • 非常感谢...我知道我忽略了一些事情!
      • numbervars/3 技巧在存在约束的情况下不再有效。
      • delete(1,[1],[1]). 成功,但应该会失败。
      • @false 我的回答非常清楚地表明,即使经过我的更正,这个实现也有问题。这个问题不值得深入探讨。
      猜你喜欢
      • 1970-01-01
      • 2017-03-22
      • 2019-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-07
      • 1970-01-01
      相关资源
      最近更新 更多