Prolog - 递归将数字添加到列表

3
我刚开始学习Prolog,对递归概念感到困惑。现在,仅出于练习的目的,我正在尝试编写一个程序,将10个数字附加到列表中,然后打印出该列表。
这个程序的自我规定是,列表必须在主述语中“声明”(我不确定这是否是Prolog中的正确单词),并调用另一个谓词来将数字附加到列表中。
这是我目前所拥有的,但我知道它行不通,因为我试图在addToList谓词的末尾重新定义List,而这在该语言中是不允许的。
% Entry point that declares a list (`List`) to store the 10 numbers
printList(List) :-
    addToList(0, List),
    writeln(List).

% Base case - once we hit 11 we can stop adding numbers to the list
addToList(11, _).

% First case - this predicate makes adding the first number easier for me...
addToList(0, List) :-
    append([], [0], NewList),
    addToList(1, NewList),
    append([],  NewList, List). % This is valid, but List will just be [0] I think..

% Cases 1-10
addToList(Value, List) :-
    append(List, [Value], NewList),
    NextVal is Value+1,
    addToList(NextVal, NewList),
    append([], NewList, List). % This is INVALID since List is already defined

这个程序需要通过以下方式启动:
printList(List).

有没有简单的方法更改我编写的错误程序,使其能够正确运行?我非常困惑如何获取存储在List中的数字。

2个回答

3

您正在以过程化的方式思考,在Prolog中,您无法更改变量。 您正在尝试自己构建列表。 在Prolog风格中,您尝试声明所需列表的约束条件。 如果nlist/2是一个给出N个数字列表的谓词,则它的属性是什么? nlist(0, [])。 如果 nlist(N, Xs) 那么 nlist(N+1, [N+1 | Xs])。 所以你只需编写这些并让Prolog负责构造。

nlist(0, []).
nlist(N, [N | Xs]) :-
    N>0, N1 is N-1,
    nlist(N1, Xs).

如果您对递归调用的过程感到困惑,可以尝试使用 trace/0trace/1。您可以通过调用 trace(nlist) 来查看以下跟踪信息中的调用过程。

?- nlist(3, X).
 T Call: nlist(3, _78)
 T Call: nlist(2, _902)
 T Call: nlist(1, _1464)
 T Call: nlist(0, _2026)
 T Exit: nlist(0, [])
 T Exit: nlist(1, [1])
 T Exit: nlist(2, [2, 1])
 T Exit: nlist(3, [3, 2, 1])
X = [3, 2, 1]

更加过程化的代码如下:

addToList(11, A, A).

% Cases 1-10
addToList(Value, List, NewList) :-
    Value < 11,  append(List, [Value], Temp),
    NextVal is Value+1,
    addToList(NextVal, Temp, NewList).

这里的中间参数是累加器。当达到11时,累加器就是答案。

?- addToList(1, [], X).
X = [1, 2, 3, 4, 5, 6, 7, 8, 9|...] 

?- addToList(5, [], X).
X = [5, 6, 7, 8, 9, 10] 

看一下样本跟nlistaddToList之间的差异。试着弄清楚这些差异是如何发生的,以及为什么会出现这些差异。

?- addToList(7, [], X).
 T Call: addToList(7, [], _33565254)
 T Call: addToList(8, [7], _33565254)
 T Call: addToList(9, [7, 8], _33565254)
 T Call: addToList(10, [7, 8, 9], _33565254)
 T Call: addToList(11, [7, 8, 9, 10], _33565254)
 T Exit: addToList(11, [7, 8, 9, 10], [7, 8, 9, 10])
 T Exit: addToList(10, [7, 8, 9], [7, 8, 9, 10])
 T Exit: addToList(9, [7, 8], [7, 8, 9, 10])
 T Exit: addToList(8, [7], [7, 8, 9, 10])
 T Exit: addToList(7, [], [7, 8, 9, 10])
X = [7, 8, 9, 10] 

谢谢,我很感激这个回答。你的解决方案简单易懂。随着我继续学习Prolog,让自己摆脱过程式思维将是一件困难的事情。 - bmb
2
你会慢慢习惯的。你也可以编写过程式风格的代码,但这样做会与Prolog环境相抗衡,而不是让它帮助你。 - rajashekar

1
这是我的解决方案:
printSeries(_,[],0):-!.
printSeries(S,[S|T],C):-
    S1 is S+1,
    C1 is C-1,
    printSeries(S1,T,C1).

?- printSeries(7,L,5).
L = [7, 8, 9, 10, 11]

谓词可用于打印任何系列,使用起始数字和想要增加的次数。一种非常简单的方法是使用计数器。第一个谓词表示,无论起始数字是什么,以及列表中有什么,如果计数器达到0,则程序应该停止(即剪切)。第二个谓词我们有起始数字和列表,告诉它你必须从起始数字开始列表,最后是计数器。接下来,我们将起始数字增加1。将计数器减1。然后通过将新值提供给谓词重新执行所有操作。
?-printSeries(1,L,10).
L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接