【问题标题】:Backtracking issue SWI-Prolog回溯问题 SWI-Prolog
【发布时间】:2019-07-06 11:45:57
【问题描述】:

我在 SWI-Prolog 中的回溯有一些问题

在我的谓词中,我有 2 个列表作为输入,结果是第三个。

我从 L1 中取出每个带有 nth0/3 的元素,然后我使用另一个我需要的谓词,所以如果 other_pred 为真,我将第二个和元素附加到第三个列表中。 我使用 fail 来强制回溯 nth0,但显然 mypred 每次都失败,我无法得到我想要的最终列表。

我也尝试将 nth0 与索引 I 一起使用,增加它,但它也使谓词失败。如果我检查每次迭代的 I 是否低于 L1 长度,也会出现同样的问题。

mypred(L1, L2, Result) :-

    nth0(_, L1, X),
    other_pred(X, L2),
    append(L2, [X], Result), fail.

【问题讨论】:

  • 你想在这里做什么?当您回溯时,绑定会被撤消,因此您不能使用失败驱动的循环来增量构建结果。
  • 好的,我可以以迭代方式或其他方式做某事吗?
  • 是的,递归地构建结果。我想你想在 L1 为空时有一个基本情况,然后在 L1 以 X 开头时有一个递归情况。other_pred/2 对 X 和 L2 做了什么?如果它可能失败,那么您需要确定整个事情是否失败,或者您是否只是从结果中省略了该项目。在后一种情况下,您将需要一个分支或单独的子句来处理这两种情况。
  • 您不能使用这样的失败循环来继续追加到列表。尝试使用基本情况的递归方法。
  • 如果您不介意使用library(yall),您可以使用maplist 快速完成此操作:mypred(L1, L2, R) :- maplist([X=Original, X=Y]>>(memberchk(X=New, L2) -> Y=New ; Y=Original), L1, R).

标签: prolog backtracking swi-prolog


【解决方案1】:

由于您没有提供other_pred/2 的代码,因此将使用member/2

mypred([H1|T1], L2, [H1|R]) :-
    member(H1,L2),
    mypred(T1,L2,R).

mypred([H1|T1], L2, R) :-
    \+ member(H1,L2),
    mypred(T1,L2,R).

mypred([],_,[]).

示例运行:

?- mypred([1,3,5], [1,2,4,5], R).
R = [1, 5] ;
false.

?- mypred([], [1,2,4,5], R).
R = [].

?- mypred([1,3,5], [], R).
R = [].

?- mypred([1,3,5], [2,4,6], R).
R = [].

?- mypred([1,3,5], [1,3,5], R).
R = [1, 3, 5] ;
false.

虽然您可以使用nth0/3,但使用列表构造函数|/2 要好得多,请参阅:Lists

在此代码中[H1|T1][H1|R] 使用列表构造函数。

此代码也使用递归。

递归子句是

mypred([H1|T1], L2, [H1|R]) :-
    member(H1,L2),
    mypred(T1,L2,R).

mypred([H1|T1], L2, R) :-
    \+ member(H1,L2),
    mypred(T1,L2,R).

因为谓词mypred/3 在子句中被调用。还因为对mypred/3 的调用是子句中的最后一次调用,所以它是tail-recursive

递归谓词的基本情况是

mypred([],_,[]).

这是如何工作的

mypred([1,3,5], [1,2,5], R).

是第一个参数的列表[1,3,5]与第一个谓词匹配

mypred([H1|T1], L2, [H1|R]) :-
    member(H1,L2),
    mypred(T1,L2,R).

通过以下统一成功

H1 = 1
T1 = [3,5]
L2 = [1,2,5]
R = _

执行子句中的下一行:

member(H1,L2)

这成功了。

子句的最后一行被执行:

mypred(T1,L2,R)

这匹配第一个谓词

mypred([H1|T1], L2, [H1|R]) :-
    member(H1,L2),
    mypred(T1,L2,R).

通过以下统一成功

H1 = 3
T1 = [5]
L2 = [1,2,5]
R = _

执行子句中的下一行:

member(H1,L2)

这失败并回溯。

由于my_pred/3 有另一个子句,因此已尝试。

mypred([H1|T1], L2, R) :-
    \+ member(H1,L2),
    mypred(T1,L2,R).

通过以下统一成功

H1 = 3
T1 = [5]
L2 = [1,2,5]
R = _

执行子句中的下一行:

\+ member(H1,L2)

这成功了。

这种为谓词尝试不同子句的模式仍在继续。此时这将跳过细节,直到使用第三个子句。

当第一个参数的列表为[]时,使用第三个子句,

mypred([],_,[]).

现在可以开始回溯了。

因为唯一可以调用第三个子句的行是like

mypred(T1,L2,R).

在第一个和第二个子句中,R[] 统一。

现在取决于调用第三个参数中列表的子句的构造方式不同。

如果使用了第二个子句,则将使用

构造第三个参数
mypred([H1|T1], L2, R)

所以列表只是原封不动地返回。

但是,如果使用第一个子句,则将使用构造第三个参数

mypred([H1|T1], L2, [H1|R])

但是这次第三个参数的结果将是执行子句时的值H1R的值相结合。所以如果H15 并且R[] 那么[H1|R][5|[]] 这是[5]


这是一个跟踪运行

mypred([1,3,5], [1,2,5], R).

让你打电话看看所有的细节。

?- trace.
[trace]  ?- mypred([1,3,5], [1,2,5], R).
   Call: (8) mypred([1, 3, 5], [1, 2, 5], _1844)
   Unify: (8) mypred([1, 3, 5], [1, 2, 5], [1|_2090])
   Call: (9) lists:member(1, [1, 2, 5])
   Unify: (9) lists:member(1, [1, 2, 5])
   Exit: (9) lists:member(1, [1, 2, 5])
   Call: (9) mypred([3, 5], [1, 2, 5], _2090)
   Unify: (9) mypred([3, 5], [1, 2, 5], [3|_2096])
   Call: (10) lists:member(3, [1, 2, 5])
   Unify: (10) lists:member(3, [1, 2, 5])
   Fail: (10) lists:member(3, [1, 2, 5])
   Redo: (9) mypred([3, 5], [1, 2, 5], _2090)
   Unify: (9) mypred([3, 5], [1, 2, 5], _2090)
   Call: (10) lists:member(3, [1, 2, 5])
   Unify: (10) lists:member(3, [1, 2, 5])
   Fail: (10) lists:member(3, [1, 2, 5])
   Redo: (9) mypred([3, 5], [1, 2, 5], _2090)
   Call: (10) mypred([5], [1, 2, 5], _2090)
   Unify: (10) mypred([5], [1, 2, 5], [5|_2096])
   Call: (11) lists:member(5, [1, 2, 5])
   Unify: (11) lists:member(5, [1, 2, 5])
   Exit: (11) lists:member(5, [1, 2, 5])
   Call: (11) mypred([], [1, 2, 5], _2096)
   Unify: (11) mypred([], [1, 2, 5], [])
   Exit: (11) mypred([], [1, 2, 5], [])
   Exit: (10) mypred([5], [1, 2, 5], [5])
   Exit: (9) mypred([3, 5], [1, 2, 5], [5])
   Exit: (8) mypred([1, 3, 5], [1, 2, 5], [1, 5])
R = [1, 5] ;
   Redo: (10) mypred([5], [1, 2, 5], _2090)
   Unify: (10) mypred([5], [1, 2, 5], _2090)
   Call: (11) lists:member(5, [1, 2, 5])
   Unify: (11) lists:member(5, [1, 2, 5])
   Exit: (11) lists:member(5, [1, 2, 5])
   Fail: (10) mypred([5], [1, 2, 5], _2090)
   Fail: (9) mypred([3, 5], [1, 2, 5], _2090)
   Redo: (9) lists:member(1, [1, 2, 5])
   Fail: (9) lists:member(1, [1, 2, 5])
   Redo: (8) mypred([1, 3, 5], [1, 2, 5], _1844)
   Unify: (8) mypred([1, 3, 5], [1, 2, 5], _1844)
   Call: (9) lists:member(1, [1, 2, 5])
   Unify: (9) lists:member(1, [1, 2, 5])
   Exit: (9) lists:member(1, [1, 2, 5])
   Fail: (8) mypred([1, 3, 5], [1, 2, 5], _1844)
false.

如果您使用的是 SWI-Prolog,则执行此查询组合以调出更便于学习的 GUI 跟踪器。

?- gtrace.
[trace]  ?- mypred([1,3,5], [1,2,5], R).   

根据评论中的建议

以下是其他一些细微的代码变化和性能指标。

mypred_01([H1|T1], L2, [H1|R]) :-
    member(H1,L2),
    mypred_01(T1,L2,R).

mypred_01([H1|T1], L2, R) :-
    \+ member(H1,L2),
    mypred_01(T1,L2,R).

mypred_01([],_,[]).


mypred_02(L1,L2,R) :-
    mypred_02_helper(L1,L2,[],R).

mypred_02_helper([H1|T1],L2,R0,R) :-
    (
        member(H1,L2)
    ->
        mypred_02_helper(T1,L2,[H1|R0],R)
    ;
        mypred_02_helper(T1,L2,R0,R)
    ).

mypred_02_helper([],_,R,R).


mypred_03(L1,L2,R) :-
    mypred_03_helper(L1,L2,[],R0),
    reverse(R0,R).

mypred_03_helper([H1|T1],L2,R0,R) :-
    (
        member(H1,L2)
    ->
        mypred_03_helper(T1,L2,[H1|R0],R)
    ;
        mypred_03_helper(T1,L2,R0,R)
    ).

mypred_03_helper([],_,R,R).


mypred_04(L1,L2,R) :-
    mypred_04_helper(L1,L2,[],R).

mypred_04_helper([H1|T1],L2,R0,R) :-
    (
        memberchk(H1,L2)
    ->
        mypred_04_helper(T1,L2,[H1|R0],R)
    ;
        mypred_04_helper(T1,L2,R0,R)
    ).

mypred_04_helper([],_,R,R).


mypred_05(L1,L2,R) :-
    mypred_05_helper(L1,L2,[],R0),
    reverse(R0,R).

mypred_05_helper([H1|T1],L2,R0,R) :-
    (
        memberchk(H1,L2)
    ->
        mypred_05_helper(T1,L2,[H1|R0],R)
    ;
        mypred_05_helper(T1,L2,R0,R)
    ).

mypred_05_helper([],_,R,R).

以下是性能结果。

?- findall(N, between(1,100000,N), L1),time(mypred_01(L1,[1,10,100,10000,100000],R)).
% 1,400,020 inferences, 0.109 CPU in 0.103 seconds (106% CPU, 12800183 Lips)
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
R = [1, 10, 100, 10000, 100000] ;
% 36 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
false.

?- findall(N, between(1,100000,N), L1),time(mypred_02(L1,[1,10,100,10000,100000],R)).
% 799,988 inferences, 0.063 CPU in 0.062 seconds (101% CPU, 12799808 Lips)
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
R = [100000, 10000, 100, 10, 1].

?- findall(N, between(1,100000,N), L1),time(mypred_03(L1,[1,10,100,10000,100000],R)).
% 800,059 inferences, 0.047 CPU in 0.053 seconds (88% CPU, 17067925 Lips)
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
R = [1, 10, 100, 10000, 100000].

?- findall(N, between(1,100000,N), L1),time(mypred_04(L1,[1,10,100,10000,100000],R)).
% 299,999 inferences, 0.031 CPU in 0.041 seconds (77% CPU, 9599968 Lips)
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
R = [100000, 10000, 100, 10, 1].

?- findall(N, between(1,100000,N), L1),time(mypred_05(L1,[1,10,100,10000,100000],R)).
% 300,005 inferences, 0.031 CPU in 0.032 seconds (98% CPU, 9600160 Lips)
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
R = [1, 10, 100, 10000, 100000].

【讨论】:

  • @User9213 这不是很有建设性的反馈。解释你不喜欢什么以及为什么不喜欢,或者发表你自己的答案,但要更有建设性。
  • 建设性反馈:这种答案对写这种问题的人来说毫无用处。在问答网站上,它们通常也没有用,因为问题本身几乎没有价值:即使你写了一个教科书质量的答案,也几乎没有人能收到。
  • @User9213 如果您不喜欢某个问题,请投票结束该问题。摧毁那些试图提供帮助的人,你没有任何好处。
  • @User9213 我不是在侮辱你。我当然不是有意侮辱你。我试图解释 Stack Overflow 的工作原理。
  • @CapelliC 谢谢。好主意,我会添加。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-07
  • 2018-09-05
相关资源
最近更新 更多