我想检查元素是否在列表中间。 我搜索中间元素然后我检查是否是列表的成员,但我得到无限循环。
我的谓词:
remove_first([_,H1|T], [H1|T]).
remove_last([_],[]).
remove_last([H|T], [H|T2]) :- remove_last(T, T2).
remove_first_and_last([X],[X]).
remove_first_and_last(In, Out) :-
remove_first(In, Out1),
remove_last(Out1, Out).
middle([X], [X]).
middle(In, X) :-
remove_first_and_last(In, Out),
middle(Out, X).
member(X, [X|_]).
member(X, [_|T]) :- member(X, T).
is_middle(X, In) :-
middle(In, Out),
member(X, Out), !.
当我打电话给is_middle(1,[2,1,3])
时,我才会成真。
但是当我打电话给is_middle(1,[2,2,3])
时,我没有得到结果。解释器不会中断处理。
答案 0 :(得分:9)
在您的情况下,您有两种选择。如你在另一个答案中所看到的那样,要么穿过痕迹文本的墙壁,要么首先尝试减少你必须理解的东西。我更喜欢后者,因为我不喜欢阅读。
但你的主要问题是这个。你说:
当我打电话给
is_middle(1,[2,1,3])
时,我就会成真。
是的,Prolog找到了一个解决方案,但它没有发现一次但是无数次。只需点击 SPACE 或; 即可看到:
?- is_middle(1,[2,1,3]).
true ;
true ;
true ;
true ;
true ;
true ...
所以,您的第一个查询已经存在问题。观察此问题的最佳方法是在此查询中添加 false
:
?- is_middle(1,[2,1,3]), false. ** LOOPS **
现在,让我们尝试减少查询的大小。我们可以将其缩小到:
?- is_middle(1,[1]), false. ** LOOPS **
有了这个,我们现在可以查看你的程序了。在其他任何事情之前,我将删除切口。无论如何它都是错位的。
要了解实际发生的情况,我会通过在其中插入 false
来缩小您的计划范围。有了这些额外的目标,就有可能消除许多不必要的细节。而且,如果它仍在循环中,那么称为failure-slice的剩余程序与我们相关。
remove_first_and_last([X],[X]).remove_first_and_last(In, Out) :- false,remove_first(In, Out1),remove_last(Out1, Out).middle([X], [X]) :- false. middle(In, X) :- remove_first_and_last(In, Out), middle(Out, X), false. is_middle(X, In) :- middle(In, Out), false,member(X, Out).
将此与您的原始程序进行比较!阅读少得多。要解决此问题,您必须修复剩余片段中的内容。我建议删除事实remove_first_and_last([X],[X]).
这个事实表明某些内容已被删除,但在这种情况下,没有任何内容被删除。
直接使用dcg的解决方案:
is_middle(E, Es) :-
phrase(middle(E), Es).
middle(E) --> [E].
middle(E) --> [_], middle(E), [_].
它尽可能短,但它有一个小问题:它不能确定地计算答案。您可以通过查看答案来看到这一点:
?- is_middle(1, [2,1,3]).
true ;
false.
此; false
表示Prolog无法确定地完成计算。换句话说,剩下一些空间。你可能会想要使用剪裁。抗蚀剂!
如果你真的速度快,请选择最快的版本:
is_middle(X, Xs) :-
Xs = [_|Cs],
middle_el(Cs, Xs, X).
middle_el([], [X|_], X).
middle_el([_,_|Cs], [_|Xs], X) :-
middle_el(Cs, Xs, X).
如果你想要@ DanielLyons'允许偶数长度列表具有两个中间元素的解释,看看采用上面的语法定义是多么容易。只需添加以下两条规则:
middle(E) --> [E,_].
middle(E) --> [_,E].
或者,将所有四个规则合并为一个:
middle(E) --> [E] | [E,_] | [_,E] | [_], middle(E), [_].
对于最快的解决方案,事情有点复杂......
is_middle_dl(X, Xs) :-
Xs = [_|Cs],
middle_el_dl(Cs, Xs, X).
middle_el_dl([], [X|_], X).
middle_el_dl([_|Cs], Xs, X) :-
middle_el_dl2(Cs, Xs, X).
middle_el_dl2([], [A,B|_], X) :-
( X = A ; X = B ).
middle_el_dl2([_|Cs], [_|Xs], X) :-
middle_el_dl(Cs, Xs, X).
要检查它,我使用SICStus,因为它为变量提供了更多可读的名称:
| ?- length(Xs, N), N mod 2 =:= 0, is_middle_dl(X, Xs).
Xs = [X,_A],
N = 2 ? ;
Xs = [_A,X],
N = 2 ? ;
Xs = [_A,X,_B,_C],
N = 4 ? ;
Xs = [_A,_B,X,_C],
N = 4 ? ;
Xs = [_A,_B,X,_C,_D,_E],
N = 6 ? ;
Xs = [_A,_B,_C,X,_D,_E],
N = 6 ? ;
Xs = [_A,_B,_C,X,_D,_E,_F,_G],
N = 8 ? ;
Xs = [_A,_B,_C,_D,X,_E,_F,_G],
N = 8 ? ;
Xs = [_A,_B,_C,_D,X,_E,_F,_G,_H,_I],
N = 10 ? ;
Xs = [_A,_B,_C,_D,_E,X,_F,_G,_H,_I],
N = 10 ? ...
答案 1 :(得分:6)
调试Prolog需要一些不同的技能,所以让我们走很长的路。
首先,让我们注意一下您的两个示例查询的一些有趣内容。第一个成功,它应该成功;第二个应该失败,而是循环。这个消息是一个线索:它表明我们正试图处理一个错误的案例。这是使用Prolog后其他语言的人们常犯的错误。在Prolog中,通常可以明确地说明成功案例,并且只是通过失败的统一来发生失败。
用于调试Prolog的标准工具是trace/0
。我们的想法是,您激活跟踪模式,然后运行您的查询,如下所示:
?- trace, is_middle(1,[2,2,3]).
trace/0
的问题在于它可能需要一些努力来了解它正在发生的事情。每行以这四个动词中的一个开头:call,exit,redo或fail。然后是一个数字,表示呼叫的嵌套级别。调用和重做动词告诉你正在进行计算;退出并失败告诉您计算停止并且嵌套级别即将降低。调用/退出是正常情况,失败/重做是Prolog特殊的,非确定性。通常,无限循环看起来像有意义工作的一些前缀(或可能不是),然后是来自trace
的无休止重复的输出块。我们在这里看到了。前缀:
Call: (8) is_middle(1, [2, 2, 3]) ? creep
Call: (9) middle([2, 2, 3], _G1194) ? creep
Call: (10) remove_first_and_last([2, 2, 3], _G1194) ? creep
Call: (11) remove_first([2, 2, 3], _G1194) ? creep
Exit: (11) remove_first([2, 2, 3], [2, 3]) ? creep
Call: (11) remove_last([2, 3], _G1197) ? creep
Call: (12) remove_last([3], _G1190) ? creep
Exit: (12) remove_last([3], []) ? creep
Exit: (11) remove_last([2, 3], [2]) ? creep
Exit: (10) remove_first_and_last([2, 2, 3], [2]) ? creep
重复大块:
Call: (10) middle([2], _G1200) ? creep
Exit: (10) middle([2], [2]) ? creep
Exit: (9) middle([2, 2, 3], [2]) ? creep
Call: (9) member(1, [2]) ? creep
Call: (10) member(1, []) ? creep
Fail: (10) member(1, []) ? creep
Fail: (9) member(1, [2]) ? creep
Redo: (10) middle([2], _G1200) ? creep
Call: (11) remove_first_and_last([2], _G1200) ? creep
Exit: (11) remove_first_and_last([2], [2]) ? creep
现在您可以看到,使用此查询触发不良行为会更容易:
[trace] ?- is_middle(2,[3]).
Call: (7) is_middle(2, [3]) ? creep
Call: (8) middle([3], _G398) ? creep
Exit: (8) middle([3], [3]) ? creep
Call: (8) member(2, [3]) ? creep
Call: (9) member(2, []) ? creep
Fail: (9) member(2, []) ? creep
Fail: (8) member(2, [3]) ? creep
Redo: (8) middle([3], _G398) ? creep
Call: (9) remove_first_and_last([3], _G398) ? creep
Exit: (9) remove_first_and_last([3], [3]) ? creep
Call: (9) middle([3], _G401) ? creep
Exit: (9) middle([3], [3]) ? creep
Exit: (8) middle([3], [3]) ? creep
Call: (8) member(2, [3]) ? creep
Call: (9) member(2, []) ? creep
Fail: (9) member(2, []) ? creep
Fail: (8) member(2, [3]) ? creep
Redo: (9) middle([3], _G401) ? creep
现在应该清楚问题与middle/2
,remove_first_and_last/2
和member/2
的相互作用有关。您对member/2
的定义完全是标准定义,因此可能不应该受到指责。现在,有趣的是,您middle/2
同时调用了自己和remove_first_and_last/2
。 middle/2
和remove_first_and_last/2
都有相同的条款:m([X], [X])
。
这种事情是一个无限递归的伟大生成器,因为{em>第二个子句中的middle/2
首先做的就是它刚刚尝试做的事情并且失败了它自己的< em> first 子句。因此,它可以发现自己在第二个子句中进入递归调用,其中与先前对自身的失败调用中的状态完全相同。
解决方案是查看remove_first_and_last/2
并意识到你的第一个子句 not 实际上删除了第一个和最后一个元素。删除remove_first_and_last([X], [X])
子句修复了代码:
[trace] ?- is_middle(2,[3]).
Call: (7) is_middle(2, [3]) ? creep
Call: (8) middle([3], _G398) ? creep
Exit: (8) middle([3], [3]) ? creep
Call: (8) member(2, [3]) ? creep
Call: (9) member(2, []) ? creep
Fail: (9) member(2, []) ? creep
Fail: (8) member(2, [3]) ? creep
Redo: (8) middle([3], _G398) ? creep
Call: (9) remove_first_and_last([3], _G398) ? creep
Call: (10) remove_first([3], _G398) ? creep
Fail: (10) remove_first([3], _G398) ? creep
Fail: (9) remove_first_and_last([3], _G398) ? creep
Fail: (8) middle([3], _G398) ? creep
Fail: (7) is_middle(2, [3]) ? creep
false.
您的测试现在也可以使用:
?- is_middle(1,[2,1,3]).
true.
?- is_middle(1,[2,2,3]).
false.
我认为你在这里添加基础案例是出于责任感。但实际情况是,如果你有一个元素的列表,它在任何情况下都不能与remove_first_and_last/2
统一。这与使用Prolog明确处理错误情况非常相似,后者往往会干扰机器的工作。
现在,缺少一件事是,你想如何处理偶数列表?无论有没有改变,你现在所拥有的都不会。偶数长度列表没有中间元素;那是你的意图吗?我怀疑它不是,因为member/2
中出现is_middle/2
。
对is_middle/2
你在这里可以重新构建如下:
is_middle(X, In) :- middle(In, [X]).
member/2
的使用并没有给你带来任何好处,因为middle/2
不能在第二个参数中产生非单例列表。但是,如果确实如此,因为你有一个长度列表,那将是有利可图的。您甚至可以通过向middle/2
添加第三个子句来使此代码以这种方式工作:
middle([X,Y], [X,Y]).
现在看middle/2
适用于偶数长度列表,如下所示:
?- middle([2,1,3,4], X).
X = [1, 3] ;
false.
现在削减让你陷入困境。例如,1和3都是is_middle/2
:
?- is_middle(1, [2,1,3,4]).
true.
?- is_middle(3, [2,1,3,4]).
true.
不幸的是,如果你要求中间元素,你只需要1
:
?- is_middle(X, [2,1,3,4]).
X = 1.
3
发生了什么事?你阻止它被切割生成。我不确定切割的原因在这里。我认为你必须把它放在试图控制无限递归,但它没有帮助你,所以摆脱它。
通过随机添加剪辑进行调试通常不是一个好主意。更好的方法是使用Ulrich Neumerkel的失败切片方法(有关详细信息,请参阅this paper或search the tag)。
DCG红利
您可以将remove_first_and_last/2
改为DCG规则:
remove_first_and_last --> remove_first, remove_last.
非常酷,对吧? :)这是因为您在该规则中正在进行的输入/输出线程类型正是DCG规则转化为的内容。
变更摘要
remove_first_and_last(In, Out) :-
remove_first(In, Out1),
remove_last(Out1, Out).
middle([X], [X]).
middle([X,Y], [X,Y]).
middle(In, X) :-
remove_first_and_last(In, Out),
middle(Out, X).
答案 2 :(得分:6)
is_middle(Item,List) :-
append(Left,[Item|Right],List),
length(Left,X),
length(Right,X).
复杂的解决方案是不好的解决方案,我的朋友。
?- is_middle(X,[1,2,3,4,5]).
X = 3 ;
false.
完全可逆的谓词:
?- is_middle(3,L).
L = [3] ;
L = [_G13, 3, _G19] ;
L = [_G13, _G19, 3, _G25, _G28] ;