Реверсирование списка в Prolog

Я закончил домашнее задание для моего программирования класса. Я должен был создать программу пролога, которая перевернет список. Однако мне трудно понять, почему именно это работает.

%1. reverse a list
%[a,b,c]->[c,b,a]

%reverse(list, rev_List).
reverse([],[]).  %reverse of empty is empty - base case
reverse([H|T], RevList):-
    reverse(T, RevT), conc(RevT, [H], RevList).  %concatenation

что именно такое RevT в этом случае? Я знаю, что он должен представлять собой обратную сторону T или остальную часть данного списка, но я не вижу, как он может иметь какое-либо значение, поскольку я его ничему не назначил. Это просто служат той же цели RevList, но для каждого рекурсивный вызов?

кроме того, почему я должен использовать [H] вместо только H в моем вызове функции conc ()? Разве H не относится к главе списка (например: [H])? Или это просто относится к элементу во главе списка (только H)?

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

4 ответов


свое решение объяснил: Если мы перевернем пустой список, мы получим пустой список. Если мы перевернем список [H / T] , мы получим список, полученный путем реверса T и объединения с [H] . Чтобы убедиться в правильности рекурсивного предложения, рассмотрим список [a,b,c, d] . Если мы перевернем конец этого списка, мы получим [d,c, b] . Объединение этого с [a] дает [d,c,b,a], что является обратным [a,b,c,d]

другое обратное решение:

 reverse([],Z,Z).

 reverse([H|T],Z,Acc) :- reverse(T,Z,[H|Acc]).

звоните:

?- reverse([a,b,c],X,[]).

для получения дополнительной информации, пожалуйста, прочитайте: http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse25


списки Prolog-это простые структуры данных:./2

  • пустой список-это атом [].
  • список из одного элемента, [a], на самом деле это структура:.(a,[]).
  • список из двух элементов, [a,b] на самом деле эту структуру: .(a,.(b,[]))
  • список из трех элементов, [a,b,c] на самом деле эту структуру: .(a,.(b,.(c,[])))
  • и так далее.

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

из этого, мы получаем понятие глава списка (datum в самом внешнем ./2 структура) и хвост список (подсписок, содержащихся в крайних ./2 структуры данных.

это, по сути, та же структура данных, которую вы видите для классического односвязного списка в C:

struct list_node
{
  char payload ;
  struct list_node *next ;
}

здесь next указатель имеет значение NULL или другую структуру списка.

Итак, из этого мы получаем простую [наивную] реализацию reverse/2:

reverse( [] , []    ) .  % the empty list is already reversed.
reverse[ [X] , [X]  ) .  % a list of 1 item is already reversed. This special case is, strictly speaking, optional, as it will be handled by the general case.
reverse( [X|Xs] , R ) :- % The general case, a list of length >= 1 , is reversed by
  reverse(Xs,T) ,        % - reversing its tail, and
  append( T , [X] , R )  % - appending its head to the now-reversed tail
  .                      %

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

однако этот алгоритм не очень эффективен: он показывает O (n2) поведение, для начала. Это также не хвост рекурсивный, что означает что список достаточной длины вызовет переполнение стека.

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

prepend( X , Xs , [X|Xs] ) .

общей идиомой в prolog является использование работник С аккумулятор. Это делает reverse/2 реализация гораздо более эффективно и (возможно) немного легче понять. Здесь мы переворачиваем список, заполняя наш аккумулятор как пустой список. Мы перебираем исходный список. Когда мы сталкиваемся с элементом в исходном списке, мы добавляем его к обратному списку, таким образом, создавая обратный список по мере продвижения.

reverse(Xs,Ys) :-            % to reverse a list of any length, simply invoke the
  reverse_worker(Xs,[],Ys) . % worker predicate with the accumulator seeded as the empty list

reverse_worker( []     , R , R     ).    % if the list is empty, the accumulator contains the reversed list
reverse_worker( [X|Xs] , T , R     ) :-  % if the list is non-empty, we reverse the list
  reverse_worker( Xs , [X|T] , R )       % by recursing down with the head of the list prepended to the accumulator
  .

теперь у вас есть reverse/2 реализация, которая выполняется в O (n) времени. Это также хвост рекурсивный, что означает, что он может обрабатывать список любой длины, не раздувая его стек.


рассмотрите возможность использования DCG вместо этого, что намного проще понять:

reverse([])     --> [].
reverse([L|Ls]) --> reverse(Ls), [L].

пример:

?- phrase(reverse([a,b,c]), Ls).
Ls = [c, b, a].

что именно такое RevT в этом случае? Я знаю, что он должен представлять собой обратную сторону T или остальную часть данного списка, но я не вижу, как он может иметь какое-либо значение, поскольку я его ничему не назначил. Служит ли он той же цели, что и RevList, но для каждого рекурсивного вызова?

переменные в прологе являются "заполнителями" для Аргументов отношений. Что мы знаем, после успешного вызова, это именно то, что указанные аргументы выполняются для это отношение.

затем RevT будет иметь значение, если вызов успешно. В частности, будет последним аргументом вызова conc(RevT, [H], RevList), , когда список не пустой. В противном случае будет пустой список.

кроме того, почему я должен использовать [H] вместо только H в моем вызове функции conc ()? Разве H не относится к главе списка (например: [H])? Или это просто относится к элементу во главе списка (только H)?

Да, H относится к первому элемент (обычно называется элемент) списка, то мы должны "изменить" его, чтобы он был списком (всего из 1 элемента), как того требует conc/3, то есть еще одно отношение между списки.