避免无限递归但仍仅使用未绑定参数传递

Avoiding infinite recursion but still using unbound parameter passing only

我有以下工作程序:(可以在这个网站上测试:http://swish.swi-prolog.org,我已经删除了直接 link 到一个保存的程序,因为我注意到任何人都可以编辑它。)

它在无向图中搜索两点之间的路径。重要的部分是结果在 "main" 谓词的范围内返回。 (在跟踪变量中)

edge(a, b).
edge(b, c).
edge(d, b).
edge(d, e).
edge(v, w).

connected(Y, X) :-
    (
        edge(X, Y);
        edge(Y, X)
    ).

path(X, X, _, []) :-
    connected(X, _).

path(X, Y, _, [X, Y]) :-
    connected(Y, X).

path(X, Z, Visited, [X|Track]) :-
    connected(X, Y),
    not(member(X, Visited)),
    path(Y, Z, [X|Visited], Track).

main(X, Y) :-
    path(X, Y, [], Track),
    print(Track),
    !.

结果:

?- main(a, e).
[a, b, d, e]
true

?- main(c, c).
[]
true

?- main(b, w).
false

我的问题:

  1. 访问过的节点列表以两种不同的方式传递给谓词。在绑定的 Visited 变量和未绑定的 Track 变量中。这两种不同形式的参数传递的名称是什么?

  2. 通常我只想使用未绑定参数传递(Track 变量),让结果在主谓词的范围内。但是我也必须添加 Visited 变量,因为成员检查对 Track 变量不起作用(我不知道为什么)。是否可以仅以未绑定的方式通过 Track 使其工作? (没有 Visited 变量)

非常感谢!

简短的回答:不,你不能避免额外的争论而不让一切变得更加混乱。这是因为这种用于寻找路径的特定算法需要保持状态;基本上,你的额外参数是你的状态。

可能还有其他方法来保持状态,例如使用全局可变变量或动态更改 Prolog 数据库,但这两种方法都更难正确使用,并且会涉及更多代码。

这个额外的参数通常被称为 累加器 ,因为它会在您向下 proof tree 时累积一些东西。最简单的例子是遍历一个列表:

foo([]).
foo([X|Xs]) :-
    foo(Xs).

这很好,除非你需要知道在到达这里之前你已经看到了哪些元素:

bar(List) :-
    bar_(List, []).

bar_([], _).
bar_([X|Xs], Acc) :-
    /* Acc is a list of all elements so far */
    bar_(Xs, [X|Acc]).

这与您在代码中所做的大致相同。如果你特别看一下这个:

path(X, Z, Visited, /* here */[X|Track]) :-
    connected(X, Y),
    not(member(X, Visited)),
    path(Y, Z, [X|Visited], /* and here */Track).

path/4 的最后一个参数在证明树中 少一个 深度处多 一个元素 !而且,当然,第三个参数更长(它随着证明树的向下而增长)。

例如,您可以通过向上面的愚蠢 bar 谓词添加另一个参数来反转列表:

list_reverse(L, R) :-
    list_reverse_(L, [], R).

list_reverse_([], R, R).
list_reverse_([X|Xs], R0, R) :-
    list_reverse_(Xs, [X|R0], R).

我不知道最后一个参数有什么特殊的名字,那个在开始时是自由的,在最后是解决方案。在某些情况下,它可能是一个 输出参数 ,因为它意味着在以某种方式转换输入之后捕获输出。在许多情况下,最好避免将参数视为严格的输入或输出参数。例如,length/2:

?- length([a,b], N).
N = 2.

?- length(L, 3).
L = [_2092, _2098, _2104].

?- length(L, N).
L = [],
N = 0 ;
L = [_2122],
N = 1 ;
L = [_2122, _2128],
N = 2 . % and so on

注意:您的代码中有很多不重要的小问题,在 Whosebug 上提供那么多建议并不是一个好主意。如果您愿意,可以将此作为问题提交到 Code Review

编辑:你一定要学习this question

我还提供了一个更简单的解决方案here。请注意在编译时使用 term_expansion/2 从无向边生成有向边。更重要的是:您不需要 main,只需从顶层调用您想要的谓词即可。当您放弃切割时,当您的 From 和 To 参数中的一个或两个是自由变量时,您将获得所有可能的解决方案。