Почему мое правило пролога застревает в бесконечной рекурсии

Мой код работает по своему прямому назначению, но всегда зацикливается в конце, что приводит к ошибке, говорящей о превышении предела стека. Мой код ниже:

byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).
   
byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).
   
byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).


travel(X,Y):- byCar(X,Y).
travel(X,Y):- byTrain(X,Y).
travel(X,Y):- byPlane(X,Y).
travel(X,Y):- travel(X,Z), travel(Z,Y).


person Sam Pepper    schedule 12.08.2020    source источник
comment
byAny(X,Y) :- byCar(X,Y) ; byTrain(X,Y) ; byPlane(X, Y). travel(X,Y) :- closure(byAny,X,Y). См. это для closure/3   -  person false    schedule 12.08.2020
comment
См. это, чтобы узнать, почему это зацикливается.   -  person false    schedule 12.08.2020


Ответы (1)


Когда вы вызываете что-то вроде travel(metz, To), последнее предложение travel/2 вызовет travel(metz, Z) с новой переменной Z, которая затем может вызвать travel(metz, Z2) с новой переменной Z2 и так далее.

Эта проблема называется левой рекурсией: у вас есть рекурсивный вызов, который эквивалентен исходной цели на всем пути слева (т. е. в начале) предложения. Решение состоит в том, чтобы добиться некоторого прогресса перед рекурсивным вызовом. В этом случае вы можете пройти один прыжок до рекурсии:

step(X, Y) :-
    byCar(X, Y).
step(X, Y) :-
    byTrain(X, Y).
step(X, Y) :-
    byPlane(X, Y).

travel(X, Y) :-
    step(X, Y).
travel(X, Z) :-
    step(X, Y),
    travel(Y, Z).

Теперь это заканчивается:

?- travel(metz, To).
To = frankfurt ;
To = paris ;
To = bangkok ;
To = singapore ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = losAngeles ;
To = auckland ;
To = hamilton ;
To = raglan ;
false.

Как указано в комментарии false, вы можете использовать общий предикат для фиксации такого закрытия: Определение рефлексивного транзитивного замыкания. В качестве альтернативы, некоторые системы Prolog предоставляют функцию, называемую таблицами, которую вы можете использовать, чтобы избежать проблем такого рода: https://www.swi-prolog.org/pldoc/man?section=tabling-non-termination

person Isabelle Newbie    schedule 12.08.2020
comment
Другой подход — добавить параметр, который отслеживает, какие места вы видели, и проверяет (используя member/2), есть ли новое место в списке. Таких примеров в учебниках много. - person Peter Ludemann; 13.08.2020
comment
Я не думаю, что это другой подход как таковой. Разве вам не нужно было бы в любом случае решать проблему левой рекурсии по существу таким же образом? Было бы здорово, если бы вы могли опубликовать альтернативный ответ! - person Isabelle Newbie; 13.08.2020
comment
Если вы измените travel(X,Y):- travel(X,Z), travel(Z,Y). на travel(X,Seen0, Seen, Y):- \+ member(X, Seen), travel(X,[X|Seen], Seen1, Z), travel(Z,[Z|Seen1], Seen,Y)., то я думаю, что это сработает (не проверял). Таблетки намного проще. ;) И, конечно же, вы также можете изменить эту строку на travel(X,Y):- travel0(X,Z), travel(Z,Y)., если вы определяете travel0(X,Y) :- byCar(X,Y). и т. д. (по сути, стратификация запросов выполняется вручную) - person Peter Ludemann; 14.08.2020