Пролог обратного просмотра и проверки ввода одновременно - PullRequest
0 голосов
/ 20 ноября 2018

Я недавно начал изучать пролог, и, хотя отойти от функционального программирования очень приятно, все еще кажется очень чуждым. У меня возникли проблемы с пониманием того, как я могу написать предикат, проверяющий, соответствует ли его аргумент определенному набору правил, и в то же время, если задана переменная, он установит для него возможные значения, которые удовлетворяют этим правилам.

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

Я определил предикат speaksSame(X, Y), который возвращает истину, если оба индивида X и Y говорят на одном языке. Теперь цель состоит в том, чтобы написать функцию для размещения таблицы так, чтобы table-seating([mark, carl, emily, kevin, oliver]) возвращало значение true, если каждые два человека, сидящие рядом друг с другом в списке, говорят на одном языке. Конечно, для этого каждый человек говорит на нескольких языках. А также стол для сидения (L). Получил бы возможные сидения стола, которые удовлетворяют условию.

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

Любая помощь будет очень признательна, спасибо!

Ответы [ 2 ]

0 голосов
/ 20 ноября 2018

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

table_seating([X,Y]) :- speaksSame(X, Y).

Итак, что, если вы добавите третьего человека в микс?Вы бы сделали что-то вроде этого:

% for exposition only; do not include this clause
table_seating([A,X,Y]) :- speaksSame(A,X), speaksSame(X, Y).

Теперь, надеюсь, вы заметите, что ваша новая работа speaksSame(A,X), но ваша старая работа осталась прежней.Давайте просто позаботимся о новом человеке и доверим , что мы могли бы обработать его до конца списка.

table_seating([X,Y,Z|Rest]) :- 
   speaksSame(X, Y),
   table_seating([Y,Z|Rest]).

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

Рекурсия почти всегда имеет такой вид:как я могу настроить минимальную правильную ситуацию, базовый случай, а затем, как я могу правильно добавить еще одну вещь в эту ситуацию?

Теперь что интересно, если вы предоставите список некоторой длины для этогоПредикат, он будет «просто работать» и производить решения такой длины.Попробуйте так:

?- length(L, 6), table_seating(L).

Возможно, вы получите решения (я предполагаю, что speaksSame/2 будет генерировать решения).Это потому, что все Пролог знает об этих переменных, о которых он знает из-за вашего предиката speaksSame/2.Таким образом, до тех пор, пока вы используете предикаты, которые имеют много шаблонов создания экземпляров в ваших предикатах и ​​не заставляют назначать вещи или странно упорядочивать вещи, часто ваши предикаты будут наследовать эти режимы.Вот почему я рекомендую людям использовать succ/2 вместо N is N0 + 1 или N0 is N - 1, потому что succ/2 определяет отношение между двумя числами вместо , выполняющего некоторую арифметику (clpfdэта идея намного, намного дальше).

0 голосов
/ 20 ноября 2018

Я не понимаю, как я могу сделать оба с одной функцией.

Да, поначалу это кажется странным, но потом, когда вы освоите его, вы упустите его на других языках.

Слово, которое вы хотите запомнить при ссылке на это: mode
Также см. Справочник по режимам Mercury для более подробной информации о языке программирования Mercury.

В Прологе аргумент может быть входным и выходным, или оба могут использоваться в качестве входных или выходных в зависимости от того, как он вызывается.

В Заголовки объявления типа, режима и детерминизма внизу перечислены 4 примера.

  1. длина (+ Список: список, -Длина: int) является дет.
  2. длина (? List: list, -Length: int) не определена.
  3. длина (? Список: список, + длина: int) является дет.
  4. Истинно, если List является списком длины Length.

и определение length/2 показывает

length(?List, ?Int)

смысл
что для аргумента List список может быть связан или не связан, и
что для аргумента Int значение может быть связано или не связано.
Таким образом, для двух аргументов с двумя опциями существует четыре способа использования length/2

Здесь они перечислены снова, но в фактическом использовании.

1

length(+List:list, -Length:int) is det.

в этом случае List связан, а Length не связан и всегда дает один ответ.

?- length([1,2,3],N).
N = 3.

2

 length(?List:list, -Length:int) is nondet.

в этом случае List не связан, а Length не связан и может вернуть любое количество ответов.

?- length(List,N).
List = [],
N = 0 ;
List = [_5362],
N = 1 ;
List = [_5362, _5368],
N = 2 ;
List = [_5362, _5368, _5374],
N = 3 ;
List = [_5362, _5368, _5374, _5380],
N = 4 ;
List = [_5362, _5368, _5374, _5380, _5386],
N = 5 
...

3.

length(?List:list, +Length:int) is det.

в этом случае List не связан, а Length связан и всегда дает один ответ.

?- length(List,4).
List = [_5332, _5338, _5344, _5350].

4

True if List is a list of length Length.

в этом случае List связан, а Length связан и всегда действует как предикат, возвращая либо true, либо false.

?- length([1,2,3],3).
true.

?- length([1,2,3],5).
false.

Так как это возможно?

Пролог использует синтаксическое объединение (↦) и НЕ назначение (=).

Если мы посмотрим на исходный код length/2, используя listing/1, мы получим

?- listing(length/2).
system:length(B, A) :-
        var(A), !,
        '$skip_list'(D, B, C),
        (   C==[]
        ->  A=D
        ;   var(C)
        ->  C\==A,
            '$length3'(C, A, D)
        ;   throw(error(type_error(list, B), context(length/2, _)))
        ).
system:length(B, A) :-
        integer(A),
        A>=0, !,
        '$skip_list'(D, B, C),
        (   C==[]
        ->  A=D
        ;   var(C)
        ->  E is A-D,
            '$length'(C, E)
        ;   throw(error(type_error(list, B), context(length/2, _)))
        ).
system:length(_, A) :-
        integer(A), !,
        throw(error(domain_error(not_less_than_zero, A),
                    context(length/2, _))).
system:length(_, A) :-
        throw(error(type_error(integer, A), context(length/2, _))).

, что слишком много деталей, но правильно выполняет все 4 режима.

Чтобы было легче понять, мы будем использовать эту версию, но она не поддерживает 1 режим правильно, но работает более чем в одном режиме, поэтому работает достаточно хорошо для демонстрации.

length_2([]     , 0 ).
length_2([_|Xs] , L ) :- 
    length_2(Xs,N),
    L is N+1 .

Теперь, чтобы увидеть объединение в действии, мы будем использовать функцию trace в SWI-Prolog и включить все порты для блочной модели , использовать visible / 1 и чтобы не останавливать его при запуске используйте поводок / 1 .

?- visible(+all),leash(-all).
?- trace.

1.

[trace] ?- length_2([1,2,3],N).
   Call: (8) length_2([1, 2, 3], _2352)
   Unify: (8) length_2([1, 2, 3], _2352)
   Call: (9) length_2([2, 3], _2580)
   Unify: (9) length_2([2, 3], _2580)
   Call: (10) length_2([3], _2580)
   Unify: (10) length_2([3], _2580)
   Call: (11) length_2([], _2580)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2584 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2590 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) _2352 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([1, 2, 3], 3)
N = 3.

2

[trace] ?- length_2(List,N).
   Call: (8) length_2(_2296, _2298)
   Unify: (8) length_2([], 0)
   Exit: (8) length_2([], 0)
List = [],
N = 0 ;
   Redo: (8) length_2(_2296, _2298)
   Unify: (8) length_2([_2528|_2530], _2298)
   Call: (9) length_2(_2530, _2550)
   Unify: (9) length_2([], 0)
   Exit: (9) length_2([], 0)
   Call: (9) _2298 is 0+1
   Exit: (9) 1 is 0+1
   Exit: (8) length_2([_2528], 1)
List = [_2528],
N = 1 ;
   Redo: (9) length_2(_2530, _2550)
   Unify: (9) length_2([_2534|_2536], _2556)
   Call: (10) length_2(_2536, _2556)
   Unify: (10) length_2([], 0)
   Exit: (10) length_2([], 0)
   Call: (10) _2560 is 0+1
   Exit: (10) 1 is 0+1
   Exit: (9) length_2([_2534], 1)
   Call: (9) _2298 is 1+1
   Exit: (9) 2 is 1+1
   Exit: (8) length_2([_2528, _2534], 2)
List = [_2528, _2534],
N = 2 ;
   Redo: (10) length_2(_2536, _2556)
   Unify: (10) length_2([_2540|_2542], _2562)
   Call: (11) length_2(_2542, _2562)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2566 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([_2540], 1)
   Call: (10) _2572 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([_2534, _2540], 2)
   Call: (9) _2298 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([_2528, _2534, _2540], 3)
List = [_2528, _2534, _2540],
N = 3 

3.

[trace] ?- length_2(List,3).
   Call: (8) length_2(_5534, 3)
   Unify: (8) length_2([_5724|_5726], 3)
   Call: (9) length_2(_5726, _5746)
   Unify: (9) length_2([], 0)
   Exit: (9) length_2([], 0)
   Call: (9) 3 is 0+1
   Fail: (9) 3 is 0+1
   Redo: (9) length_2(_5726, _5746)
   Unify: (9) length_2([_5730|_5732], _5752)
   Call: (10) length_2(_5732, _5752)
   Unify: (10) length_2([], 0)
   Exit: (10) length_2([], 0)
   Call: (10) _5756 is 0+1
   Exit: (10) 1 is 0+1
   Exit: (9) length_2([_5730], 1)
   Call: (9) 3 is 1+1
   Fail: (9) 3 is 1+1
   Redo: (10) length_2(_5732, _5752)
   Unify: (10) length_2([_5736|_5738], _5758)
   Call: (11) length_2(_5738, _5758)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _5762 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([_5736], 1)
   Call: (10) _5768 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([_5730, _5736], 2)
   Call: (9) 3 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([_5724, _5730, _5736], 3)
List = [_5724, _5730, _5736] 
Action (h for help) ? abort

% Execution Aborted

4.

[trace] ?- length_2([1,2,3],3).
   Call: (8) length_2([1, 2, 3], 3)
   Unify: (8) length_2([1, 2, 3], 3)
   Call: (9) length_2([2, 3], _2058)
   Unify: (9) length_2([2, 3], _2058)
   Call: (10) length_2([3], _2058)
   Unify: (10) length_2([3], _2058)
   Call: (11) length_2([], _2058)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2062 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2068 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) 3 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([1, 2, 3], 3)
true.

[trace] ?- length_2([1,2,3],5).
   Call: (8) length_2([1, 2, 3], 5)
   Unify: (8) length_2([1, 2, 3], 5)
   Call: (9) length_2([2, 3], _2442)
   Unify: (9) length_2([2, 3], _2442)
   Call: (10) length_2([3], _2442)
   Unify: (10) length_2([3], _2442)
   Call: (11) length_2([], _2442)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2446 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2452 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) 5 is 2+1
   Fail: (9) 5 is 2+1
   Fail: (8) length_2([1, 2, 3], 5)
false.

и отключить трассировку

[trace] ?- notrace.
true.

[debug] ?- nodebug.
true.

Я не буду проходить каждую из строк в выводе трассировки, но если вы понимаете синтаксическое объединение и можете следовать трассировке, после проработки приведенных примеров вы увидите, как объединяются переменные в Prolog, что приводит к различным режимам, когда по сравнению с императивным программированием.

Помните, что переменные связаны только один раз в Прологе и никогда не переназначаются, и что числа в левой части трассы в скобках, например, (10) - уровень стека, поэтому, когда снова делается вызов предикату, становится доступным новый набор переменных, и, хотя может показаться, что ему присваивается значение, на самом деле это другая переменная в стеке, просто в другом стековом фрейме .

В качестве отступления при изучении Пролога, один совет, который я даю, состоит в том, что легче учиться, если вы откладываете то, что знаете об императивном и функциональном программировании, за исключением, возможно, рекурсии, и начинаете с нуля с объединения, а затем обратная цепочка .

Если вы можете прочитать OCaml, здесь - это упрощенная версия объединения и обратной цепочки. Обратите внимание, что это не Пролог, так как в нем нет списка или оператора вырезки, но если вы можете это понять, то пути объединения и обратного сцепления становятся очевидными.

Я должен добавить, что я не полностью удовлетворен своим ответом, так как знаю, что вы новичок, и этот ответ предназначен для усвоения большого количества информации и требует от вас большой работы для прохождения 4 примеров трассировки. Однако он отвечает на вопрос и дает практический пример с более чем достаточным количеством мяса на кости. Я работаю над попыткой придумать лучший пример, который включал бы логическая чистота и который демонстрирует, что не только объединение, но и отношения являются ключом к тому, как несколько режимов могут быть достигнуты в одном предикате. Радуйтесь, что я не использовал общую теорию относительности, как перефразировал релятивист Джон Джон Арчибальд Уилер, spacetime tells matter how to move; matter tells spacetime how to curve.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...