【问题标题】:How to get Prolog to explain your result beyond the true statement如何让 Prolog 在真实陈述之外解释你的结果
【发布时间】:2015-06-04 16:54:25
【问题描述】:

我有以下事实和规则:

  flight(sea,msp).
  flight(msp,jfk).

 route(A,B) :- flight(A,B). 
 route(B,A) :- flight(A,B). 
 route(A,C) :- flight(A,B) , flight(B,C). 

当查询route(sea,jfk) 我得到一个结果true 但我希望得到的是解释:

sea-->msp-->jfk 这样我不仅可以判断它是真的,还可以判断它是如何正确的。

【问题讨论】:

    标签: prolog


    【解决方案1】:

    所以你想从AB,但不仅如此,你还想知道你行程的站点列表。

    请务必仔细查看以下两个相关问题以及针对该问题提出的答案:

    以上链接中提供的元谓词允许您将递归处理委托给可靠的、经过测试的、可重用的组件。有更多时间专注于解决问题的其他部分!

    【讨论】:

      【解决方案2】:

      您可以跟踪图表中已经访问过的节点。无论如何,您都需要这样做,因为您需要在图表中检测 循环,以免陷入无限递归的兔子洞。

      在 Prolog 中,我们使用 helper 方法来携带状态作为 1 个或多个额外参数。一个常用的约定是有一个“公共”谓词——比如route/3,它调用一个具有更高数量的同名的“私有”工作者谓词,比如route/4。你应该这样做:

      route( A , B , R  ) :- % find a route R from A to B
        route(A,B,[],R)      % - by invoking a worker, seeding its list of visited nodes with the empty list
        .                    % Easy!
      
      route(B,B,V,R) :-    % we've arrived at the destination (B) when the origination node is the same as the destination node.
        reverse([B|V],R)   % - just reverse the list of visited nodes to get the routing.
        .                  %
      route(A,B,V,R) :-    % otherwise...
        flight(A,T) ,      % - if there's an edge out of the current node (A) ,
        \+ member(T,V) ,   % - to an as-yet unvisited node...
        route(T,B,[A|V],R) % - go visit that node, marking the current node as visited.
        .                  % Easy!
      

      【讨论】:

      • 谢谢,我消除了路线(A,C):- 航班(A,B),航班(B,C)。规则,因为您添加了它。任何了解“调用工人”和列表的好链接?再次感谢。
      • 获得一份 Sterling & Shapiro's The Art Of Prolog 的副本并完成它将是一个好的开始。比 Clocksin & Mellish 好得多(如果你问我的话)。另请参阅 Richard O'Keefe 的 The Craft of Prolog,这不是介绍性文字。更多的是关于技术和优雅。
      • 谢谢 Nicholas,非常感谢!
      【解决方案3】:

      如果您既不想使用调试,也不想使用辅助谓词编写其他方法,第三种选择是利用 SWI-Prolog 的许多内置功能进行元编程。在这种情况下,clause/2 predicate 可能会有所帮助(它是 ISO 标准的一部分,因此其他 Prolog 方言可能也有,但我没有检查过):

      子句(:Head, ?Body)

      如果Head 可以与子句头部统一,Body 可以与相应的子句主体统一,则为真。给出关于回溯的替代条款。事实上,Body 与原子true 统一。

      所以我们可以编写一个通用谓词expl(+Goal,-Expl),为Goal 构造一个“解释”,以防Goal 成功:

      flight(sea,msp).
      flight(msp,jfk).
      
      route(A,B) :- flight(A,B). 
      route(B,A) :- flight(A,B). 
      route(A,C) :- flight(A,B) , flight(B,C). 
      
      % construct an explanation for a solution
      expl([],[]).
      expl([Goal|Goals],[(Goal,BodyExpl)|Expl]) :-
              clause(Goal,Body),
              clause_body_list(Body,BodyL),
              expl(BodyL,BodyExpl),
              expl(Goals,Expl).
      
      % turn output of clause/2 into a list
      clause_body_list(true,[]) :- !.
      clause_body_list((A,B),[A|BL]) :- !,
              clause_body_list(B,BL).
      clause_body_list(A,[A]) :- !.
      

      这会产生:

      ?- expl([route(sea,jfk)],X).
      X = [(route(sea, jfk), [(flight(sea, msp), []),  (flight(msp, jfk), [])])].
      

      它还支持带变量的回溯和查询:

      ?- expl([route(A,B)],X).
      A = sea,
      B = msp,
      X = [(route(sea, msp), [(flight(sea, msp), [])])] ;
      A = msp,
      B = jfk,
      X = [(route(msp, jfk), [(flight(msp, jfk), [])])] ;
      A = msp,
      B = sea,
      X = [(route(msp, sea), [(flight(sea, msp), [])])] ;
      A = jfk,
      B = msp,
      X = [(route(jfk, msp), [(flight(msp, jfk), [])])] ;
      A = sea,
      B = jfk,
      X = [(route(sea, jfk), [(flight(sea, msp), []),  (flight(msp, jfk), [])])] ;
      false.
      

      请注意,这样的解释不一定是列表,而是通常采用(SLD)树的形式,因此是输出的嵌套结构。

      编辑:进一步解释上述内容:输出是(Goal, BodyExpl) 形式的“解释”列表,其中每个Goal 是已证明的(子)目标,并且BodyExpl 再次是用于证明Goal 的所有递归子目标的此类解释的列表。事实上,BodyExpl 部分是空的。一般来说,这个结构可以嵌套任意深度(取决于你的输入程序)。

      如果您觉得这很难阅读,对进一步处理输出不感兴趣,并且只想要一个人类可读的解释,您可以执行以下操作:

      flight(sea,msp).
      flight(msp,jfk).
      
      route(A,B) :- flight(A,B). 
      route(B,A) :- flight(A,B). 
      route(A,C) :- flight(A,B) , flight(B,C). 
      
      % construct an explanation for a solution
      expl([]).
      expl([Goal|Goals]) :-
              clause(Goal,Body),
              clause_body_list(Body,BodyL),
              expl(BodyL),
              expl(Goals),
              write_expl(Goal,Body).
      
      % turn output of clause/2 into a list
      clause_body_list(true,[]) :- !.
      clause_body_list((A,B),[A|BL]) :- !,
              clause_body_list(B,BL).
      clause_body_list(A,[A]) :- !.
      
      % write explanation
      write_expl(Goal, true) :- !,
              writef('%w is a fact.\n',[Goal]).
      write_expl(Goal, Body) :- !,
              writef('%w because of %w.\n', [Goal,Body]).
      

      这会产生例如:

      ?- expl([route(sea,jfk)]).
      flight(msp,jfk) is a fact.
      flight(sea,msp) is a fact.
      route(sea,jfk) because of flight(sea,msp),flight(msp,jfk).
      

      请注意,您只想expl 进行递归调用后调用write_expl,因为某些变量可能仅在递归调用期间被实例化。

      【讨论】:

      • 可移植性说明 大多数 Prolog 系统只允许在动态谓词上使用 clause/2
      【解决方案4】:

      这在很大程度上取决于您的 prolog 系统。由于您已将其标记为 swi,因此我将给您一个特定于 SWI 的答案。

      您可以启动跟踪器。与trace/0

      ?: trace.
      true
      
      [trace]?:
      

      当您现在输入查询时,您可以看到谓词的所有调用、退出、失败和重做。但是,您无法在命令行跟踪器中看到变量名称。要查看您可以采取的操作,您可以输入h。最有趣的可能是n 用于下一步,f 用于完成当前目标。

      或者也可以利用trace/1trace/2输出部分调用栈:

      ?: trace(flight/2). % calls, exits, fails and redos are output for flight/2
      
      ?: trace(route/2, +exit).  % only exits are output for route/2.
      

      如果您还安装了 xpce,则可以使用gtrace/0 来作为图形界面。

      如果你想从 prolog 中访问你的路线,你也可以写一个新的route/3 来输出路线列表。

      因此,对于您的情况,您可以执行以下查询:

      ?- trace(flight/2,+exit).
      %         flight/2: [exit]
      true.
      
      [debug]  ?- route(sea,jfk).
       T Exit: (7) flight(sea, msp)
       T Exit: (7) flight(msp, jfk)
      true.
      

      【讨论】:

        猜你喜欢
        • 2014-06-14
        • 2021-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 2019-07-25
        • 2013-10-14
        相关资源
        最近更新 更多