Prolog — рекурсивное добавление чисел в список

#list #recursion #prolog

#Список #рекурсия #пролог

Вопрос:

Я только начинаю изучать Prolog, и у меня возникают проблемы с тем, чтобы обдумать рекурсивные концепции. Прямо сейчас, исключительно для практики, я пытаюсь написать программу, которая добавляет 10 чисел в список, а затем распечатывает этот список.

Самоналоженное правило для этой программы заключается в том, что список должен быть «объявлен» (я не уверен, что это правильное слово для Prolog) в главном предикате, который вызывает другой предикат для добавления чисел в список.

Это то, что у меня есть до сих пор, и я знаю, что это не сработает, потому что я пытаюсь переопределить List в конце addToList предиката, что запрещено в языке.

 % 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 .

Ответ №1:

Вы думаете процедурно, в 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/0 или trace/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] 
  

Посмотрите на образец трассировки и разницу между ними в nlist и addToList . Попытайтесь выяснить различия и почему это происходит.

 ?- 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] 
  

Комментарии:

1. Спасибо, я ценю этот ответ. Ваше решение простое и понятное. Заставить себя уйти от моего процедурного мышления будет сложно, поскольку я продолжаю изучать Prolog..

2. Вы постепенно привыкнете к этому. Вы также можете написать код в процедурном стиле, но вы будете бороться против среды prolog, а не позволять ей помогать вам.

Ответ №2:

Вот мое решение:

 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]