Реверсирование списка в 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, то есть еще одно отношение между списки.