Создание структуры очереди в Прологе - PullRequest
2 голосов
/ 29 февраля 2020

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

Я пытаюсь выполнить следующие инструкции:

Реализация поэтапной очереди. Это структура, состоящая из двух списков: внешний конец и внутренний конец , представленный как queue(Front, Back). Очередь пуста если оба списка пусты. Если элементы добавляются в очередь, они добавляются в бэкэнд; если они удалены, они удалены от переднего конца; если передний конец становится пустым (и задний конец не пуст), то задний конец становится новым передним концом с [] в качестве нового внутреннего конца. Например, мы могли бы начать с очереди, такой как queue([2,5,7], []), добавление 8 дает нам queue([2,5,7], [8]), удаление двух элементов дает queue([7], [8]), добавление 9 дает queue([7], [9,8]), а удаление элемента дает queue([9,8], [])

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

Я в общих чертах набросал то, что, по моему мнению, мне следует делать, как определенную структуру очереди, так и просто список списков.

add_to_q(X, [[H|T]|[H2|T2]], [[H|T]|[X|[H2|T2]]).

queue(X, Y)
add_to_q(A, queue(X,Y), queue(X, [A|Y]). % gives Syntax error: Operator expected

------------------

remove_from_q( [[H | [T|T3]] | [H2|T2]], [[T|T3]] | [H2|T2]]).

queue(X, Y)
remove_from_q( queue(X,[H|T]), queue(H,T).

Как определить и работать со структурой в Прологе, как добавить то, что было бы на языке OO Методы , такие как getHead или getTail, I видел примеры того, как вы могли бы сделать это только со списками, но я работаю не со списком списков, а с «очередью» из двух отдельных списков?

Чувство потеряно!

Ответы [ 3 ]

1 голос
/ 01 марта 2020

Это забавная очередь. Когда задняя часть очереди перемещается вперед, элементы появляются в обратном порядке. Это не совсем очередь.

Тем не менее, операции довольно просты.

Для начала представим очередь в виде составного термина queue(X,Y), где X и Y являются списками, представляющими начало и конец очереди.

Нам нужен способ получить пустую очередь:

empty(queue([],[])).

Чтобы добавить элемент в очередь, мы делаем это:

add(A,queue(X,Y),queue(X,[A|Y])).

Теперь инструкции противоречивы. Они говорят, что два пустых списка представляют пустую очередь, и новые элементы всегда добавляются в черный список. Так как же первый список может стать пустым , если он начинается пустым и никогда не добавляется в?

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

remove(queue([],[A|X]),A,queue(X,[])).

Это переносит хвост списка назад вперед и возвращает первый элемент хвоста в A.

Наконец, если передний список не пуст, тогда мы делаем это:

remove(queue([A|T],X),A,queue(T,X)).

Давайте проверим эти 4 предиката.

?-
    empty(Q),
    add(7,Q,Q2),
    add(5,Q2,Q3),
    add(2,Q3,Q4),
    write(Q4),nl,
    remove(Q4,A,Q5),
    add(3,Q5,Q6),
    remove(Q6,B,Q7),
    remove(Q7,C,Q8),
    remove(Q8,D,Q9),
    empty(Q9),
    write([A,B,C,D]).

Q9 должен быть пустым после добавления 4 атомов в очередь и затем удаления их. [A,B,C,D] унифицируется до [2,5,7,3].

Q4 - это начальное состояние, описанное в инструкциях, хотя оно queue([],[2,5,7]), поскольку мы всегда добавляемся в черный список.

Так что все это ведет себя так, как нам было приказано.

Но это не настоящая очередь!

Если это была истинная очередь, то первый добавленный элемент должен быть первым удаленным элементом , Ожидаемый результат будет [7,5,2,3]. Чтобы это работало, мы переписали бы первый предикат remove следующим образом:

remove(queue([],Z),A,queue(X,[])) :- reverse(Z,[A|X]).
reverse(X,Y):- reverse(X,[],Y).
reverse([],A,A).
reverse([H|T],A,R) :- reverse(T,[H|A],R). 

Выполнение кода с этим изменением дает нам ожидаемый результат очереди.

1 голос
/ 05 марта 2020

Что Гай Кодер сказал. Забудь оО ничего. Все дело в сопоставлении с образцом.

Это все, что вам нужно:

  • Во-первых, средство для создания пустой очереди:
empty( q([],[]) ).
  • Как только у вас это есть, поставить что-либо в очередь - тривиально. Это просто вопрос добавления элемента в очередь в очередь «назад»:

    enque( X , q(F,B) , q(F, [X|B] ) ) .
    

    Объяснение: Списки в прологе являются рекурсивными структурами данных. По соглашению пустой список обозначается как атом [] (воспринимается как строка). Непустые списки пишутся в квадратных скобках ([1], [1,2], et c). Но ... это просто синтаксис c сахар для структуры данных ./2:

    • [1] - сокращение для .( 1 , [] )
    • [1,2] сокращение для .( 1 , .( 2 , [] ) )
    • ... и т. Д.

    Вы можете оценить полезность квадратной скобки. Если вы посмотрите на эту рекурсивную структуру данных, вы заметите, что список состоит из одного элемента, за которым следует другой [вложенный] список. Этот единственный элемент является заголовком списка, а вложенный список - его tail .

    nota bene: рекурсивность является важной концепцией в прологе. Научиться думать о вещах с точки зрения рекурсии поможет вам МНОГО.

    Обозначение [H|T] - это способ разложения списка на его заголовок и его хвост . И это просто syntacti c сахар для .(H,T). Это означает, что его также можно использовать для создания списков с помощью композиции.

    Так что наш предикат enqueue/3 использует его для создания нового "заднего" списка с добавлением X к нему. .

  • И, наконец, удаление чего-либо не намного сложнее:

    deque( q( []     , Bs ) , F , q( Fs , [] ) ) :- reverse( Bs, [F|Fs] ).
    deque( q( [F|Fs] , Bs ) , F , q( Fs , Bs ) ) .
    

    Объяснение:

    Первый Термин в предикате имеет дело со случаем исключения из очереди, когда «передний» список пуст. Мы просто переворачиваем задний список, берем его 1-й элемент, head (F) в качестве объекта, находящегося в очереди, и захватываем его tail (Fs) в качестве нового "фронта". "список. И мы даем новой очереди пустой список «назад».

    И второе слагаемое в предикате касается случая удаления из очереди, когда «передний» список не пуст. Еще проще: мы просто берем head из "переднего" списка, объединяем его в качестве объекта в очереди и создаем новую очередь, используя tail старого "переднего" списка в качестве новый «передний» список, оставляя «задний» список как есть.

Пример

Запустите его на https://swish.swi-prolog.org/p/phased-queue.pl


empty( q([],[]) ).

enque( X , q(F,B) , q(F, [X|B] ) ).

deque( q( []     , Bs ) , F , q(Fs,[]) ) :- reverse( Bs, [F|Fs] ).
deque( q( [F|Fs] , Bs ) , F , q(Fs,Bs) ).


run_example :-
  empty( Q0             ) , writeln( queue :         Q0  ) ,
  enque( 1   , Q0 , Q1  ) , writeln( enque :         Q1  ) ,
  enque( 2   , Q1 , Q2  ) , writeln( enque :         Q2  ) ,
  enque( 3   , Q2 , Q3  ) , writeln( enque :         Q3  ) ,
  enque( 4   , Q3 , Q4  ) , writeln( enque :         Q4  ) ,
  deque( Q4  , X1 , Q5  ) , writeln( deque : x(X1) : Q5  ) ,
  enque( 5   , Q5 , Q6  ) , writeln( enque :         Q6  ) ,
  enque( 6   , Q6 , Q7  ) , writeln( enque :         Q7  ) ,
  deque( Q7  , X2 , Q8  ) , writeln( deque : x(X2) : Q8  ) ,
  deque( Q7  , X3 , Q9  ) , writeln( deque : x(X3) : Q9  ) ,
  deque( Q9  , X4 , Q10 ) , writeln( deque : x(X4) : Q10 ) ,
  deque( Q10 , X5 , Q11 ) , writeln( deque : x(X5) : Q11 ) ,
  deque( Q11 , X6 , Q12 ) , writeln( deque : x(X6) : Q12 ) ,
  deque( Q12 , X7 , Q13 ) , writeln( deque : x(X7) : Q13 ) ,
  empty( Q13            )
  .

1 голос
/ 01 марта 2020

У меня проблемы с приближением к языку, исходящему из ОО-фона.

Сделайте себе одолжение и забудьте о том, что вы знаете об ОО во время изучения Пролога, это еще больше запутает вас при изучении пролога. Другими словами, не думайте о концепции ОО, тогда как я перевел это на Пролог. Подумайте о syntacti c унификации как о том, как строить все более и более сложные предикаты.


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

Инструкции дают основу для структуры данных, то есть

queue(Front,Back)

а Front и Back - это список. Примеры списка

[]
[a]
[a,b]
[a|b]

Ссылаться на очередь несложно. Поскольку Prolog использует объединение syntacti c, одна сторона объединения - это атом, с которым вы хотите объединиться, например, queue(Front,Back), а другая сторона объединения - это преобразование queue(Front,Back), вы можете просто использовать их в предикате как

Вы продемонстрировали это уже с

add_to_q(A,queue(X,Y),queue(X,[A|Y])

Обратите внимание, что в нем отсутствует окончание ).


add_to_q(A,queue(X,Y),queue(X,[A|Y]). % gives Syntax error: Operator expected

В нем отсутствует окончание ).

add_to_q(A,queue(X,Y),queue(X,[A|Y])).

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

Здесь это рабочий код, основанный на вопросе.

add(Item,queue(Front,Back),queue(Front,[Item|Back])).

remove(Item,queue([Item|[]],Back),queue(Back,[])).
remove(Item,queue([Item|Front],Back),queue(Front,Back)).

:- begin_tests(funny_queue).

funny_queue_test_case_generator(add   , 8    ,queue([2,5,7]  ,[   ] ) ,queue([2,5,7],[8]   ) ).
funny_queue_test_case_generator(remove, 2    ,queue([2,5,7]  ,[  8] ) ,queue([5,7]  ,[8]   ) ).
funny_queue_test_case_generator(remove, 5    ,queue([5,7]    ,[  8] ) ,queue([7]    ,[8]   ) ).
funny_queue_test_case_generator(add   , 9    ,queue([7]      ,[  8] ) ,queue([7]    ,[9,8] ) ).
funny_queue_test_case_generator(remove, 7    ,queue([7]      ,[9,8] ) ,queue([9,8]  ,[]    ) ).

test(add,[forall(funny_queue_test_case_generator(add,Item,Funny_queue_0,Funny_queue))]) :-
    add(Item,Funny_queue_0,Funny_queue_expected),

    assertion( Funny_queue == Funny_queue_expected ).

test(add,[nondet,forall(funny_queue_test_case_generator(remove,Item,Funny_queue_0,Funny_queue))]) :-
    remove(Item,Funny_queue_0,Funny_queue_expected),

    assertion( Funny_queue == Funny_queue_expected ).

:- end_tests(funny_queue).
...