我开始Prolog(仅为我自己),我正在努力进行递归。
我想要一个"方法",它在列表中的特定位置插入一个元素。 到目前为止我尝试的是:
TypeError: element.selectize is not a function
我觉得我很复杂,因为我无法理解递归究竟是如何工作的......
也许有人可以帮助我。
答案 0 :(得分:3)
您的代码有几个功能使初学者难以理解。特别是,当以有趣(实际上也是严肃的)方式与节目互动时,使用模式化的低级算术是一个严重的障碍。
例如,要理解关系,从最常见的查询开始是很有用的。这只是问“有没有任何解决方案,如果有,解决方案是什么样的?”。在您的特定示例中,最常见的查询如下所示:
?- insertAt(E, Pos, Ls0, Ls).
由于您使用的非声明性算术谓词,这几乎立即产生instantiation error
:
?- insertAt(E, Pos, Ls0, Ls).
Pos = 0,
Ls = [E|Ls0] ;
ERROR: insertAt/4: Arguments are not sufficiently instantiated
此外,您通过使用命令性名称(“插入...”)来阻止一个很好的声明性读取。这使得开发关系编程的感觉变得不必要。
因此,我建议你:(1)使用更具声明性的谓词名称,(2)使用自然数的符号表示,使谓词更容易理解,更通用。
我将使用数字0
代表零,而s(X)
代表数字X
的后继代号。有关此表示的详细信息,请参阅successor-arithmetics。
通过这些更改,代码变为:
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, s(X), [L|Ls0], [L|Ls]) :-
element_at(E, X, Ls0, Ls).
为了理解这个程序,我们以声明的方式阅读:第一个句子是 true 如果位置是0
,并且最终列表的头部是E
,以及尾巴...等。第二个句子是 true 如果 element_at(E, X, Ls0, Ls)
成立,以及......的负责人等。
很好,最常见的查询现在效果更好:
?- element_at(E, Pos, Ls0, Ls). Pos = 0, Ls0 = [_G1071|_G1072], Ls = [E|_G1072] ; Pos = s(0), Ls0 = [_G1073, _G1079|_G1080], Ls = [_G1073, E|_G1080] ; Pos = s(s(0)), Ls0 = [_G1073, _G1081, _G1087|_G1088], Ls = [_G1073, _G1081, E|_G1088] .
请注意,这里有一些不公平的事情:剩余职位的答案在哪里?对于更公平的枚举,我们使用length/2
,提前说明我们正在考虑的列表的长度:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls). Ls0 = [_G1124], Pos = 0, Ls = [E] ; Ls0 = [_G1124, _G1127], Pos = 0, Ls = [E, _G1127] ; Ls0 = [_G1124, _G1127], Pos = s(0), Ls = [_G1124, E] .
现在更清楚的是不同的参数如何相互作用,因为你已经看到了谓词描述的各种术语示例。
事实上,为了减少我们需要跟踪的参数和变量名称的数量,我经常在描述列表时使用DCG notation,我也想向您展示这个替代版本:
element_at(Element, 0, [_|Ls]) -->
[Element],
list(Ls).
element_at(Element, s(X), [L|Ls]) -->
[L],
element_at(Element, X, Ls).
list([]) --> [].
list([L|Ls]) --> [L], list(Ls).
?- length(Ls0, _), phrase(element_at(E, Pos, Ls0), Ls). Ls0 = [_G1148], Pos = 0, Ls = [E] ; Ls0 = [_G1148, _G1151], Pos = 0, Ls = [E, _G1151] ; Ls0 = [_G1148, _G1151], Pos = s(0), Ls = [_G1148, E] .
一旦您阅读dcg符号,此版本就会变得清晰。
最后,您可能会说“嗯,这很好,但s(X)
符号似乎仍然很奇怪”,您可能希望在程序中使用更广泛使用的印度语 - 阿拉伯语符号表示整数。
为此,我们可以简单地从上面获取任一版本,并通过声明整数算法用CLP(FD)约束替换s(X)
符号。例如,使用第一个版本:
:- use_module(library(clpfd)).
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, X, [L|Ls0], [L|Ls]) :-
X #> 0,
X #= X0 + 1,
element_at(E, X0, Ls0, Ls).
这也适用于所有方向,正如我们对一个很好的声明性和一般谓词所期望的那样:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls). Ls0 = [_G2095], Pos = 0, Ls = [E] ; Ls0 = [_G2095, _G2098], Pos = 0, Ls = [E, _G2098] ; Ls0 = [_G2095, _G2098], Pos = 1, Ls = [_G2095, E] .
请参阅clpfd以获取更多信息,我希望这篇文章鼓励您更多地考虑您的Prolog代码,在各个方向进行尝试,并以声明方式阅读。 (正在描述什么?)
答案 1 :(得分:1)
Prolog功能是模式匹配,即基于谓词参数的规则选择。这个特征是Prolog表示法的关键,允许对关系进行简洁描述,特别是对于递归术语,如列表。请注意,列表只是语法糖'对于递归术语,使用传统的仿函数(术语&名称,每天用语)。
insertAt(Element,0,L,[Element|L]). % ok
insertAt(Element,Pos,[E|L],[E|ZL]):- % you forgot to cons back E
Pos1 is Pos-1,
insertAt(Element,Pos1,L,ZL). % done, append is useless
%append(E,ZL1,ZL).
SWI-Prolog有nth1 / 4和nth0 / 4,可以执行插入:
?- nth0(1,L,x,[1,2,3]).
L = [1, x, 2, 3].
答案 2 :(得分:1)
让same_length/2
,append/3
和length/2
负责递归!
insertAt(E,N,Xs,Ys) :- same_length([E|Xs],Ys), append(Before,Xs0,Xs), length(Before,N), append(Before,[E|Xs0],Ys).
示例查询:
?- insertAt(X, N, [a,b,c,d,e], Ys). ( N = 0, Ys = [X,a,b,c,d,e] ; N = 1, Ys = [a,X,b,c,d,e] ; N = 2, Ys = [a,b,X,c,d,e] ; N = 3, Ys = [a,b,c,X,d,e] ; N = 4, Ys = [a,b,c,d,X,e] ; N = 5, Ys = [a,b,c,d,e,X] ; false ).