Prolog 调用了错误的规则。无法正确回溯

发布于 2025-01-03 23:24:48 字数 1286 浏览 2 评论 0原文

怎么了?
我在使用 Prolog 时遇到了一些非常奇怪的问题。
用于替换列表中给定索引处的元素的递归规则并不总是有效。
我的规则如下所示:

% Base rule - Stops when index is 1 and replaces the head with the element.
replaceAtIndex(1, _element, [_|_tail], [_element|_tail]).

% Recursive rule - Enter recursion for tail as long as index is larger than 1.
replaceAtIndex(_index, _element, [_head|_tail], [_head|_new_tail]):-
    _index > 1,
    _new_index is _index - 1,
    replaceAtIndex(_new_index, _element, _tail, _new_tail).

当我在程序中使用调试器时,我看到无论索引是什么,它总是调用第二条规则,但是当我在程序外部执行完全相同的命令时,它会起作用非常好。它到达索引 1,但调用第二条规则,并且不会回溯并尝试第一条规则,并且一路返回失败...

调用 ReplaceAtIndex 的规则如下所示:

level_replace_block_value(_x, _y, _value):-
    current_level(_level_number, _width, _height, _blocks, _drawX, _drawY),
    coordinates_to_index(_x, _y, _index),
    _index_in_list is _index + 1, % the replaceAtIndex is not 0 terminated
    replaceAtIndex(_index_in_list, _value, _blocks, _new_blocks),
    retractall(current_level(_,_,_,_,_,_)),
    assert(current_level(_level_number, _width, _height, _new_blocks, _drawX, _drawY),
    graphics_update_block_value(_x, _y).

当我调试其索引为 111 的调用时。
当我用常量 111 替换 _index_in_list 时,它就起作用了。

任何人都可能知道为什么会发生这种情况?

Whats up?
I'm having some really weird problems with Prolog.
A recursive rule to replace an element in a list at a given index isn't always working.
My rule looks like this:

% Base rule - Stops when index is 1 and replaces the head with the element.
replaceAtIndex(1, _element, [_|_tail], [_element|_tail]).

% Recursive rule - Enter recursion for tail as long as index is larger than 1.
replaceAtIndex(_index, _element, [_head|_tail], [_head|_new_tail]):-
    _index > 1,
    _new_index is _index - 1,
    replaceAtIndex(_new_index, _element, _tail, _new_tail).

When I use the debugger from within my program I see its always calling the second rule no matter what the index is, but when I execute the exact same command outside my program it works perfectly well. It reaches index 1 but calls the second rule, and does NOT backtrack and attempt the first rule and fails all the way back up...

The rule calling the replaceAtIndex looks like this:

level_replace_block_value(_x, _y, _value):-
    current_level(_level_number, _width, _height, _blocks, _drawX, _drawY),
    coordinates_to_index(_x, _y, _index),
    _index_in_list is _index + 1, % the replaceAtIndex is not 0 terminated
    replaceAtIndex(_index_in_list, _value, _blocks, _new_blocks),
    retractall(current_level(_,_,_,_,_,_)),
    assert(current_level(_level_number, _width, _height, _new_blocks, _drawX, _drawY),
    graphics_update_block_value(_x, _y).

When I'm debugging its calling with index being 111.
When I'm replacing the _index_in_list with a constant 111 it works.

Anyone might have a clue why that happens?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

忆梦 2025-01-10 23:24:48

使用内置谓词 same_length/2length/2append/3

replace_at(I,X,Xs0,Xs2) :-
   same_length(Xs0,Xs2),
   append(Prefix,[_|Xs1],Xs0),
   length([_|Prefix],I),
   append(Prefix,[X|Xs1],Xs2).

首先,让我们运行@magus 在这个问题的之前的答案中使用的示例查询:

?- replace_at(3,0,[1,2,3,4,5,6],Xs).
Xs = [1,2,0,4,5,6] ;
false.

当列表项时它是否有效稍后实例化?

?- replace_at(3,0,[A,B,C,D,E,F],Xs), A=1,B=2,C=3,D=4,E=5,F=6.
A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, Xs = [1,2,0,4,5,6] ;
false.

是的!如果索引不是具体的整数,而是未绑定的逻辑变量怎么办? 有效吗?

?- replace_at(I,x,[_,_,_,_,_],Ys).
I = 1, Ys = [ x,_B,_C,_D,_E] ;
I = 2, Ys = [_A, x,_C,_D,_E] ;
I = 3, Ys = [_A,_B, x,_D,_E] ;
I = 4, Ys = [_A,_B,_C, x,_E] ;
I = 5, Ys = [_A,_B,_C,_D, x] ;
false.

确实如此!使用单调代码,即使对于非常一般的查询,我们也能得到逻辑上合理的答案

Preserve by using the builtin predicates same_length/2, length/2 and append/3!

replace_at(I,X,Xs0,Xs2) :-
   same_length(Xs0,Xs2),
   append(Prefix,[_|Xs1],Xs0),
   length([_|Prefix],I),
   append(Prefix,[X|Xs1],Xs2).

First, let's run the sample query that @magus used in a previous answer to this question:

?- replace_at(3,0,[1,2,3,4,5,6],Xs).
Xs = [1,2,0,4,5,6] ;
false.

Does it work when the list items are instantiated later?

?- replace_at(3,0,[A,B,C,D,E,F],Xs), A=1,B=2,C=3,D=4,E=5,F=6.
A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, Xs = [1,2,0,4,5,6] ;
false.

Yes! What if the index isn't a concrete integer, but an unbound logical variable? Does that work?

?- replace_at(I,x,[_,_,_,_,_],Ys).
I = 1, Ys = [ x,_B,_C,_D,_E] ;
I = 2, Ys = [_A, x,_C,_D,_E] ;
I = 3, Ys = [_A,_B, x,_D,_E] ;
I = 4, Ys = [_A,_B,_C, x,_E] ;
I = 5, Ys = [_A,_B,_C,_D, x] ;
false.

It does! With monotone code, we get logically sound answers even with very general queries.

牛↙奶布丁 2025-01-10 23:24:48

我怀疑你误解了回溯发生的方向。

对于任何对 replaceAtIndex/4 的调用,都将首先尝试第一个“基本”规则。如果由于第一个规则的“head”调用的不统一性而失败,则 Prolog 引擎将回溯到第二个规则。 [统一失败可能是由于第一个参数(索引)不同于 1 或第三个参数不是非空列表而导致。]

回溯永远不会朝另一个方向进行。如果尝试第二条规则失败,则调用失败。

当然,递归定义使事情变得复杂。成功应用第二条规则需要对 replaceAtIndex/4 进行新的调用,就 Prolog 引擎而言,必须从第一条规则开始尝试满足该目标。

我建议在第一条规则中添加一个削减,因为通过构造,如果第一条规则成功,第二条规则将永远不会成功。但这只是一个效率问题......为什么要留下一个永远不会产生任何进一步解决方案的选择点?

replaceAtIndex(1, _element, [_|_tail], [_element|_tail]) :- !.

添加:我确认您的代码可以在 Amzi 中运行! Prolog 的调用如下:

?- replaceAtIndex(3, 0, [1,2,3,4,5,6], L).

L = [1, 2, 0, 4, 5, 6] ;
no
?- 

但是,当然,当代码在独立/解释模式下调用时,您也会看到成功。

所以我不得不怀疑传入的“index”参数不是整数而是浮点数。整数 1 不会与浮点数 1.0 统一,即使它们在数学上相等。

您可以使用谓词 is_integer/1 在 Amzi 中对此进行测试!序言。函数integer(X)可用于通过截断(不存在的)小数部分将实数 1.0 转换为整数 1。

I suspect you misunderstand the direction in which backtracking will take place.

The first "base" rule will be tried first for any call to replaceAtIndex/4. If it fails, due to non-unifiability of the call with the "head" of the first rule, then the Prolog engine backtracks to the second rule. [Unification failure might result either from the first argument (index) differing from 1 or from the third argument not being a nonempty list.]

Backtracking never goes in the other direction. If the second rule is tried and fails, the call fails.

Of course things are complicated by the recursive definition. The success of applying the second rule entails a new call to replaceAtIndex/4, which as far as the Prolog engine is concerned must begin attempting to satisfy that goal by starting back at the first rule.

I'd suggest adding a cut to the first rule, since by construction the second rule will never succeed if the first rule does. But this is just an efficiency issue... why leave a choicepoint open that will never produce any further solutions?

replaceAtIndex(1, _element, [_|_tail], [_element|_tail]) :- !.

Added: I confirmed that your code works in Amzi! Prolog with a call like this:

?- replaceAtIndex(3, 0, [1,2,3,4,5,6], L).

L = [1, 2, 0, 4, 5, 6] ;
no
?- 

But of course you also see success when the code is called in standalone/interpreted mode.

So I have to suspect the "index" argument being passed in is not an integer but rather a floating point number. Integer 1 will not unify with floating point 1.0, even if they are mathematically equal.

You can use the predicate is_integer/1 to test for this in Amzi! Prolog. The function integer(X) can be used to convert real 1.0 to an integer 1 by truncating the (nonexistent) fractional part.

尸血腥色 2025-01-10 23:24:48

尝试将 _index_in_list 设置为 1。那么第一条规则不会被调用吗?

当然,如果您的 _index_in_list111,则调用第二条规则的原因是 111 大于 1 ?第一条规则仅处理 1 - 正如第一个参数所述。

当我将 _index_in_list 替换为常量 111
有效。

什么?你的意思是第一条规则被调用?怎么会呢,第一个参数是1

Try _index_in_list set to 1. Wouldn't the first rule get called then?

Sure the reason why the second rule is getting called if your _index_in_list is 111 is because 111 is greater than 1 ? The first rule only deals with 1 - as the first parameter says.

When I'm replacing the _index_in_list with a constant 111 it
works.

What? You mean the first rule gets called? How can that be, the first param is 1.

愁以何悠 2025-01-10 23:24:48
replaceAtIndex(I, Value, List, Result) :-
    RI is I-1,
    findall(Before, (nth0(B, List, Before), B < RI), Before),
    findall(After, (nth0(A, List, After), A > RI), After),
    append([Before, [Value], After], Result).


?- replaceAtIndex(3,0,[1,2,3,4,5,6], L).
L = [1, 2, 0, 4, 5, 6].
replaceAtIndex(I, Value, List, Result) :-
    RI is I-1,
    findall(Before, (nth0(B, List, Before), B < RI), Before),
    findall(After, (nth0(A, List, After), A > RI), After),
    append([Before, [Value], After], Result).


?- replaceAtIndex(3,0,[1,2,3,4,5,6], L).
L = [1, 2, 0, 4, 5, 6].
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文