我已完成编程课的作业。我应该创建一个反转列表的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具有相同的用途,但对于每个递归调用?
另外,为什么我必须在我的conc()函数调用中使用[H]而不是H? H不是指列表的头部(例如:[H])?或者它只是引用列表头部的项目(只是H)?
请帮我解决这个问题。我正在努力理解这种编程背后的逻辑。
您的解决方案解释说:如果我们反转空列表,我们将获得空列表。如果我们反转列表[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,[])))
方括号表示法是一种语法糖,可以让你不必花费你的生命打字括号。更不用说它在眼睛上更容易了。
由此,我们得到列表头部的概念(最外面的./2
结构中的数据)和列表的尾部(包含在最外面的./2
数据结构中的子列表)。
这基本上是您在C中看到的经典单链表的相同数据结构:
struct list_node
{
char payload ;
struct list_node *next ;
}
其中next
指针是NULL或其他列表结构。
那么,从那以后,我们得到了反向/ 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)行为。它也不是尾递归,这意味着足够长度的列表将导致堆栈溢出。
应该注意的是,要将一个项目附加到一个prolog列表需要遍历整个列表,由于prolog列表的结构,前置是一个简单的操作。我们可以将项目添加到现有列表中,如下所示:
prepend( X , Xs , [X|Xs] ) .
prolog中常见的习语是使用带有累加器的worker谓词。这使得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
.
现在你有一个在O(n)时间内运行的reverse/2
实现。它也是尾递归的,这意味着它可以处理任何长度的列表而不会吹掉它的堆栈。
考虑使用DCG,这更容易理解:
reverse([]) --> [].
reverse([L|Ls]) --> reverse(Ls), [L].
例:
?- phrase(reverse([a,b,c]), Ls).
Ls = [c, b, a].
在这种情况下,RevT究竟是什么?我知道它应该代表T的反向或给定列表的其余部分,但是我没有看到它如何具有任何值,因为我没有将它分配给任何东西。它是否与RevList具有相同的用途,但对于每个递归调用?
Prolog中的变量是关系论证的“占位符”。我们知道,在成功调用之后,正是指定的参数适用于该关系。
如果通话成功,那么RevT
将有一个值。具体来说,当列表不为空时,将是调用conc(RevT, [H], RevList)
的最后一个参数。否则,将是空列表。
另外,为什么我必须在我的conc()函数调用中使用[H]而不是H? H不是指列表的头部(例如:[H])?或者它只是引用列表头部的项目(只是H)?
是的,H指的是列表的第一个项目(通常称为元素),然后我们必须“重塑”它作为一个列表(只有1个元素),如conc / 3所要求的那样,这是列表中的另一个关系。
只是关于测试reverse/2
谓词定义的注释,太长而不适合评论。
反转列表是引入QuickCheck的“hello world”示例,这意味着您可以使用它来帮助测试您的定义。首先,我们定义一个保存reverse/2
谓词的属性:两次反转列表必须给出原始列表,我们可以将其转换为:
same_list(List) :-
reverse(List, Reverse),
reverse(Reverse, ReverseReverse),
List == ReverseReverse.
使用Logtalk的lgtunit
工具QuickCheck实现:
% first argument bound:
| ?- lgtunit::quick_check(same_list(+list)).
% 100 random tests passed
yes
% both arguments unbound
| ?- lgtunit::quick_check(same_list(-list)).
% 100 random tests passed
yes
或者干脆:
% either bound or unbound first argument:
| ?- lgtunit::quick_check(same_list(?list)).
% 100 random tests passed
yes
但我们需要另一个属性定义来测试第二个参数bound:
same_list_2(Reverse) :-
reverse(List, Reverse),
reverse(List, ListReverse),
Reverse == ListReverse.
我们现在可以这样做:
% second argument bound:
| ?- lgtunit::quick_check(same_list_2(+list)).
% 100 random tests passed
yes
但请注意,这种基于属性的/随机化测试不会检查非终止情况,因为这些只会在第一个解决方案后回溯时发生。
以下是reverse / 2的典型实现。然而,它确实存在明显低于“非终止”的问题。
?- ['/dev/tty'] .
reverse(_source_,_target_) :-
reverse(_source_,_target_,[]) .
reverse([],_target_,_target_) .
reverse([_car_|_cdr_],_target_,_collect_) :-
reverse(_cdr_,_target_,[_car_|_collect_]) .
end_of_file.
.
?- reverse([],Q) .
Q = []
?- reverse([a],Q) .
Q = [a]
?- reverse([a,b],Q) .
Q = [b,a]
?- reverse([a,b,c],Q) .
Q = [c,b,a]
?- reverse(P,[]) .
P = [] ? ;
%% non-termination ! %%
^CAction (h for help): a
?- reverse(P,[a]) .
P = [a] ? ;
%% non-termination ! %%
^CAction (h for help): a
?- reverse(P,[a,b]) .
P = [b,a] ? ;
%% non-termination ! %%
^CAction (h for help): a
?- reverse(P,[a,b,c]) .
P = [c,b,a] ? ;
%% non-termination ! %%
^CAction (h for help): a
:- op(2'1,'fy','#') .
:- op(2'1,'fy','##') .
:- op(2'1,'fy','###') .
/ *以下是我刚刚发明的reverse / 2的实现,它没有遇到reverse(P,[])
的非终止问题。 * /
reverse(_source_,_target_) :-
reverse(_source_,_target_,_source_,_target_,[],[]) .
reverse(_source_,_target_,[],[],_target_,_source_) .
reverse(_source_,_target_,[_source_car_|_source_cdr_],[_target_car_|_target_cdr_],_source_collect_,_target_collect_) :-
reverse(_source_,_target_,_source_cdr_,_target_cdr_,[_source_car_|_source_collect_],[_target_car_|_target_collect_]) .
/*
?- reverse([],Q) .
Q = []
?- reverse([a],Q) .
Q = [a]
?- reverse([a,b],Q) .
Q = [b,a]
?- reverse([a,b,c],Q) .
Q = [c,b,a]
/*
?- reverse(P,[]) .
P = []
?- reverse(P,[a]) .
P = [a]
?- reverse(P,[a,b]) .
P = [b,a]
?- reverse(P,[a,b,c]) .
P = [c,b,a]
*/
/*
?- reverse(P,Q) .
P = Q = [] ? ;
P = Q = [_A] ? ;
P = [_A,_B],
Q = [_B,_A] ? ;
P = [_A,_B,_C],
Q = [_C,_B,_A] ? ;
P = [_A,_B,_C,_D],
Q = [_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E],
Q = [_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F],
Q = [_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G],
Q = [_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H],
Q = [_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I],
Q = [_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J],
Q = [_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K],
Q = [_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L],
Q = [_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M],
Q = [_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N],
Q = [_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O],
Q = [_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
Q = [_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q],
Q = [_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R],
Q = [_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S],
Q = [_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T],
Q = [_T,_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T,_U],
Q = [_U,_T,_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ?
*/