Viết chương trình Prolog chuyển một danh sách phức hợp, là danh sách mà mỗi phần tử có thể là một danh sách con chứa các danh sách con phức hợp khác, thành một danh sách phẳng là danh [r]
(1)95 CHƯƠNG
Cấu trúc danh sách
Chương trình bày khái niệm danh sách, cấu trúc đơn giản thơng dụng nhất, với chương trình tiêu biểu minh hoạ cách vận dụng danh sách Prolog Cấu trúc danh sách tạo nên môi trường lập trình thuận tiện ngơn ngữ Prolog
I. Biểu diễn cấu trúc danh sách
Danh sách kiểu cấu trúc liệu sử dụng rộng rãi ngơn ngữ lập trình phi số Một danh sách dãy đối tượng Khác với kiểu liệu tập hợp, đối tượng danh sách trùng (xuất nhiều lần) vị trí xuất đối tượng có ý nghĩa
Danh sách cách diễn đạt ngắn gọn kiểu liệu hạng phức hợp Prolog Hàm tử danh sách dấu chấm “.” Do việc biểu diễn danh sách hàm tử tạo biểu thức mập mờ, xử lý danh sách gồm nhiều phần tử lồng nhau, Prolog quy ước đặt dãy phần tử danh sách cặp móc vng
Chẳng hạn (a,.(b,[ ])) Là danh sách [ a, b ]
Danh sách phần tử anne, tennis, tom, skier (tên người) viết : [ anne, tennis, tom, skier ]
chính hàm tử :
( anne, .( tennis, .( tom, .( skier, [ ] ) ) ) )
Cách viết dạng cặp móc vng xuất bên danh sách Như thấy mục trước, đối tượng cấu trúc Prolog có biểu diễn Danh sách không nằm ngoại lệ, có cấu trúc
Làm cách để biểu diễn danh sách đối tượng Prolog chuẩn ? Có hai khả xảy danh sách rỗng khơng Nếu danh sách rỗng, viết dạng nguyên tử :
(2)Lập trình lơgic Prolog 96 Nếu danh sách khác rỗng, xem cấu trúc từ hai thành phần (pair syntax) :
1 Thành phần thứ nhất, gọi đầu (head) danh sách
2 Thành phần thứ hai, phần lại danh sách (trừ phần đầu), gọi đuôi (tail) danh sách, danh sách
Trong ví dụ đầu anne, cịn đuôi danh sách : [ tennis, tom, skier ]
Nói chung, đầu danh sách đối tượng Prolog, biến, đuôi phải danh sách Hình I.1 Biểu diễn dạng danh sách mơ tả cấu trúc danh sách cho :
Hình I.1 Biểu diễn dạng danh sách
Vì tail danh sách, nên tail rỗng, lại tạo thành từ đầu head đuôi tail khác
Chú ý danh sách rỗng xuất số hạng, phần tử cuối xem danh sách gồm phần tử có phần danh sách rỗng:
[ skier ]
Ví dụ minh hoạ nguyên lý cấu trúc liệu tổng quát Prolog áp dụng cho danh sách có độ dài tuỳ ý
?- L1 = [ a, b, c ]. ?- L2 = [ a, a, a ]. L1 = [ a, b, c ] L2 = [ a, a, a ]
?- Leisure1 = [ tennis, music, [ ] ]. ?- Leisure2 = [ sky, eating ],
?- L = [ anne, Leisure1, tom, Leisure2 ] Leisure1 = [ tennis, music ]
Leisure2 = [ sky, eating ]
L = [ anne, [ tennis, music ], tom, [ sky, eating ] ]
anne đuôi danh sách đầu tennis
tom
(3)Cấu trúc danh sách 97 Như vậy, phần tử danh sách đối tượng có kiểu bất kỳ, kể kiểu danh sách Thông thường, người ta xử lý đuôi danh sách danh sách Chẳng hạn, danh sách :
L = [ a, b, c ] viết :
tail = [ b, c ] L = .(a, tail)
Để biểu diễn danh sách tạo thành từ đầu (Head) đuôi (Tail), Prolog sử dụng ký hiệu | (split) để phân cách phần đầu phần đuôi sau :
L = [ a | Tail ]
Ký hiệu | dùng cách tổng quát cách viết số phần tử tuỳ ý danh sách trước | danh sách phần tử lại Danh sách viết lại sau :
[ a, b, c ] = [ a | [ b, c ] ] = [ a, b | [ c ] ] = [ a, b, c | [ ] ]
Sau số cách viết danh sách :
Kiểu hai thành phần Kiểu liệt kê phần tử
[ ] [ ]
[ a | [ ] ] [ a ]
[ a | b | [ ] ] [ a, b ]
[ a | X ] [ a | X ]
[ a | b | X ] [ a, b | X ]
[ X1 | [ [ Xn | [ ] ] ] ] [ X1, , Xn ] Ta định nghĩa danh sáchtheo kiểu đệ quy sau : List [ ]
List [ Element | List ]
II. Một số vị từ xử lý danh sách Prolog
SWI-Prolog có sẵn số vị từ xử lý danh sách sau :
Vị từ Ý nghĩa
append(List1, List2,
List3) Ghép hai danh sách
List1 List2 thành List3
member(Elem, List)
Kiểm tra Elem có phần tử danh sách List hay
không, nghĩa Elem hợp với
phần tử List
nextto(X, Y, List) Kiểm tra phần tử Y có đứng sau phần tử X
(4)Lập trình lơgic Prolog 98
delete(List1, Elem, List2)
Xoá khỏi danh sách List1 phần tử hợp với Elemđể trả kết quảList2
select(Elem, List, Rest)
Lấy phần tửElem khỏi danh sách Listđể trả
những phần tử lại Rest, dùng để chèn phần tử vào danh sách
nth0(Index, List, Elem)
Kiểm tra phần tử thứ Index (tính từ 0) danh sách List có phải Elem hay không
nth1(Index, List, Elem)
Kiểm tra phần tử thứIndex (tính từ 1) danh sách List có phải Elem hay khơng
last(List, Elem) Kiểm tra phần tửđứng cuối danh sách List có phải Elem hay khơng
reverse(List1, List2)
Nghịch đảo thứ tự phần tử danh sách List1để
trả kết quảList2 permutation(List1,
List2) Hoán vị danh sách
List1 thành danh sách List2
flatten(List1, List2)
Chuyển danh sách List1 chứa phần tử
thành danh sách phẳng List2
Ví dụ : flatten([a, [b, [c, d], e]], X)
cho kết quảX = [a, b, c, d, e]
sumlist(List, Sum) Tính tổng phần tử của danh sách List chứa toàn
sốđể trả kết quảSum numlist(Low, High,
List)
Nếu Low High số cho Low =< High,
trả danh sách List = [Low, Low+1, , High]
Chú ý số vị từ xử lý danh sách sử dụng cho ràng buộc, kể tham đối biến
Trong Prolog, tập hợp biểu diễn danh sách, nhiên, thứ tự phần tử tập hợp không quan trọng, đối tượng dù xuất nhiều lần xem phần tử tập hợp Các phép tốn danh sách áp dụng cho tập hợp Đó :
• Kiểm tra phần tử có mặt danh sách tương tự việc kiểm tra phần tử có thuộc tập hợp khơng ?
• Ghép hai danh sách để nhận danh sách thứ ba tương ứng với phép hợp hai tập hợp
(5)Cấu trúc danh sách 99 Prolog có sẵn số vị từ xử lý tập hợp sau :
Vị từ Ý nghĩa
is_set(Set) Kiểm tra Set có phải tập hợp hay không
list_to_set(List, Set)
Chuyển danh sách List thành tập hợp Set giữ
nguyên thứ tự phần tử List (nếu List có
phần tử trùng lấy phần tử gặp đầu tiên) Ví dụ : list_to_set([a,b,a], X) cho kết X = [a,b]
intersection(Set1,
Set2, Set3) Phép giao hai tập hợp
Set1 Set2 Set3
subtract(Set, Delete, Result)
Trả kết phép hiệu hai tập hợp Set Delete Result (là tập Set sau xoá hết
phần tử Delete có mặt đó)
union(Set1, Set2, Set3) Trả kết phép hợp hai tập hợp Set1 Set2 Set3
subset(Subset, Set) Kiểm tra tập hợp Subset có tập hợp Set
hay không
III. Các thao tác danh sách III.1.Xây dựng lại số vị từ có sẵn
Sau ta trình bày số thao tác danh sách cách xây dựng lại số vị từ có sẵn Prolog
III.1.1. Kiểm tra phần tử có mặt danh sách Prolog kiểm tra phần tử có mặt danh sách sau : member(X, L)
trong đó, X phần tử L danh sách Đích member(X, L) thoả mãn X xuất L Ví dụ :
?- member( b, [ a, b, c ] ) Yes
?- member( b, [ a, [ b, c ] ] ) No
?- member( [ b, c], [ a, [ b, c ] ] ) Yes
(6)Lập trình lôgic Prolog 100 Phần tử X thuộc danh sách L :
1 X đầu L,
2 X phần tử L
Ta viết hai điều kiện thành hai mệnh đề, mệnh đề thứ kiện đơn giản, mệnh đề thứ hai luật :
member( X, [ X | Tail ] )
member( X, [ Head | Tail ] ) :- member( X, Tail ) :
member(X, [X|T])
member(X, [_|T]) :- member(X, T)
III.1.2. Ghép hai danh sách
Để ghép hai danh sách, Prolog có hàm : append( L1, L2, L3)
trong đó, L1 L2 hai danh sách, L3 danh sách kết phép ghép L1 L2 Ví dụ :
?- append( [ a, b ], [ c, d ], [ a, b, c, d ] ) Yes
?- append( [ a, b ], [ c, d ], [ a, b, a, c ] ) No
Hình III.1 Ghép hai danh sách [ X | L1 ] L2 thành [ X | L3 ]
Hàm append hoạt động phụ thuộc tham đối L1 theo cách sau : Nếu tham đối danh sách rỗng, tham đối thứ hai thứ ba phải
là danh sách nhất, gọi L Ta viết Prolog sau : append( [ ], L, L)
2 Nếu tham đối append danh sách khác rỗng, gồm đầu đuôi sau
[ X | L1 ]
X L3
X L1 L2
[ X | L1 ]
L3
(7)Cấu trúc danh sách 101 Kết phép ghép danh sách danh sách [ X | L3 ], với L3 phép ghép L1 L2 Ta viết Prolog sau :
append( [ X | L1 ], L2, [ X | L3 ] ) :- append( L1, L2, L3 )
Hình 4.2 minh hoạ phép ghép hai danh sách [ X | L1 ] L2 Ta có ví dụ sau :
?- append( [ a, b, c ], [ 1, 2, ], L ) L = [ a, b, c, 1, 2, ]
?- append( [ a, [ b, c ], d ], [ a, [ ], b ], L ] ) L = [ a, [ b, c ], d, a, [ ], b ]
Thủ tục append sử dụng mềm dẻo theo nhiều cách khác
Chẳng hạn Prolog đưa bốn phương án để phân tách danh sách cho thành hai danh sách sau :
?- append( L1, L2, [ a, b, c ] ) L1 = [ ]
L2 = [ a, b, c ]; L1 = [ a ]
L2 = [ b, c ]; L1 = [ a, b ] L2 = [ c ];
L1 = [ a, b, c ] L2 = [ ];
Yes
Sử dụng append, ta tìm kiếm số phần tử danh sách Chẳng hạn, từ danh sách tháng năm, ta tìm tháng đứng trước tháng cho, giả sử tháng năm (May) :
?- append( Before, [ May | After ] ,
[ jan, fev, mar, avr, may, jun, jul, aut, sep, oct, nov, dec ] ).
Before = [ jan, fev, mar, avr ]
After = [ jun, jul, aut, sep, oct, nov, dec ] Yes
Tháng đứng trước tháng đứng sau tháng năm nhận sau : ?- append( _, [ Month1, may, Month2 | _ ] ,
(8)Lập trình lơgic Prolog 102 Month1 = avr
Month2 = jun Yes
Bây cho trước danh sách :
L1 = [ a, b, z, z, c, z, z, z, d, e ]
Ta cần xóa phần tử đứng sau ba chữ z liên tiếp, kể ba chữ z : ?- L1 = [ a, b, z, z, c, z, z, z, d, e ],
append( L2, [ z, z, z | _ ], L1 ). L1 = [ a, b, z, z, c, z, z, z, d, e ] L2 = [ a, b, z, z, c ]
Hình III.2 Thủ tục member1 tìm đối tượng danh sách cho Trước ta định nghĩa quan hệ member( X, L ) để kiểm tra phần tử X có mặt danh sách L không Bây cách sử dụng append, ta định nghĩa lại member sau :
member1( X, L ) :- append( L1, [ X | L2], L) member1( b, [ a, b, c ]
)
Mệnh đề append So khớp : L1 = [ X | L1’ ]
[ b | L2 ] = L2’
[ a, b, c ] = [ X | L3’ ]
Từđó kéo theo : X = a, L3’ = [ b, c ]
Mệnh đề append So khớp :
L1’ = [ ]
[ b | L2 ] = [ b, c ] Từđó kéo theo : L2 = [ c ]
thành công
Mệnh đề append So khớp :
L1 = [ ]
[ b | L2 ] = [ a, b, c ] Thất bại b ≠ a
append( L1, [ b | L2 ], [ a, b, c ] )
(9)Cấu trúc danh sách 103 Mệnh đề có nghĩa : X có mặt danh sách L L phân tách thành hai danh sách, với X đầu danh sách thứ hai Định nghĩa member1 hoàn toàn tương đương với định nghĩa member
Ở ta sử dụng hai tên khác để phân biệt hai cách cài đặt Prolog Ta định nghĩa lại member1 cách sử dụng biến nặc danh (anonymous variable) :
member1( X, L ) :
-append( _ , [ X | _ ], L)
So sánh hai cách cài đặt khác quan hệ thành viên, ta nhận thấy nghĩa thủ tục định nghĩa member thể rõ :
Trong member, để kiểm tra phần tử X có mặt danh sách L không, Trước tiên kiểm tra phần tử đầu L đồng với X, khơng, Kiểm tra X có mặt phần đuôi L
Nhưng trường hợp định nghĩa member1, ta thấy hồn tồn nghĩa khai báo mà khơng có nghĩa thủ tục
Để hiểu cách member1hoạt động nào, ta xem xét trình Prolog thực câu hỏi :
?- member1( b, [ a, b, c ] )
Cách tìm thủ tục member1 đây tương tự member, cách duyệt phần tử, tìm thấy đối tượng cần tìm, danh sách cạn
III.1.3. Bổ sung phần tử vào danh sách
Phương pháp đơn giản để bổ sung phần tử vào danh sách đặt vị trí đầu tiên, để trở thành đầu Nếu X đối tượng mới, L danh sách cần bổ sung thêm, danh sách kết :
[ X | L ]
Người ta không cần viết thủ tục để bổ sung phần tử vào danh sách Bởi việc bổ sung biểu diễn dạng kiện cần :
insert( X, L, [ X | L ] )
III.1.4. Loại bỏ phần tử khỏi danh sách
Để loại bỏ phần tử X khỏi danh sách L, người ta xây dựng quan hệ : remove( X, L, L1 )
trong đó, L1 đồng với L, sau X bị loại bỏ khỏi L Thủ tục remove có cấu trúc tương tự member Ta lập luận sau
(10)Lập trình lơgic Prolog 104 Nếu khơng, tìm cách loại bỏ X khỏi phần danh sách
remove( X, [ X | Tail ], Tail )
remove( X, [ Y | Tail ], [ Y | Tail1 ] ) : -remove( X, Tail, Tail1 )
Tương tự thủ tục member, thủ tục remove mang tính khơng xác định Nếu có nhiều phần tử X có mặt danh sách, remove xố phần tử nào, trình quay lui Tuy nhiên, lần thực hiện, remove xố phần tử X mà khơng đụng đến phần tử khác Ví dụ :
?- remove( a, [ a, b, a, a ], L ). L = [ b, a, a ];
L = [ a, b, a ]; L = [ a, b, a ] No
Thủ tục remove thất bại danh sách khơng chứa phần tử cần xố Người ta sử dụng remove khía cạnh khác, mục đích để bổ sung phần tử vào đâu danh sách
Ví dụ, ta muốn đặt phần tử a vào vị trí danh sách [ 1, 2, ], cần đặt câu hỏi : Cho biết danh sách L sau xoá a, ta nhận danh sách [ 1, 2, ] ?
?- remove( a, L, [ 1, 2, ] ). L = [ a, 1, 2, ];
L = [ 1, a, 2, ]; L = [ 1, 2, a, ]; L = [ 1, 2, 3, a ] No
Một cách tổng quát, phép toán chèn insert phần tử X vào danh sách List định nghĩa thủ tục remove cách sử dụng danh sách lớn LargerList làm tham đối thứ hai :
insert( X, List, LargerList ) : -remove( X, LargerList, List )
Ta định nghĩa quan hệ thuộc thủ tục member1 cách sử dụng thủ tục append Tuy nhiên, ta định nghĩa lại quan hệ thuộc thủ tục member2 thủ tục remove cách xem phần tử X thuộc danh sách List X bị xoá khỏi List :
member2( X, List ) :
(11)Cấu trúc danh sách 105
III.1.5. Nghịch đảo danh sách
Sử dụng append, ta viết thủ tục nghịch đảo danh sách sau : reverse ( [ ], [ ] )
reverse ( [ X | Tail ], R ) : -reverse (Tail, R1 ), append(R1, [X], R)
?- reverse( [ a, b, c , d, e, f ] , L) L = [f, e, d, c, b, a]
Yes
Sau thủ tục khác để nghịch đảo danh sách có sử dụng hàm bổ trợ thân thủ tục :
revert(List, RevList) :-
rev(List, [ ], RevList) rev([ ], R, R)
rev([H|T], S, R) :- rev(T, [H|S], R)
?- revert( [ a, b, c , d, e, f ] , R) R = [f, e, d, c, b, a]
Yes
Sử dụng reverse, ta kiểm tra danh sách có đối xứng (palindrome) hay không :
palindrome(L) :-
reverse( L, L )
?- palindrome([ a, b, c , d, c, b, a ]) Yes
III.1.6. Danh sách
Ta xây dựng thủ tục sublist nhận hai tham đối hai danh sách L S cho S danh sách L sau :
?- sublist( [ c, d, e ], [ a, b, c , d, e, f ] ) Yes
?- sublist( [ c, e ], [ a, b, c , d, e, f ] ) No
(12)Lập trình lơgic Prolog 106
Hình III.3 Các quan hệmember sublist
Quan hệ danh sách mô tả sau : S danh sách L :
1 Danh sách L phân tách thành hai danh sách L1 L2, Danh sách L2 phân tách thành hai danh sách S L3
Như thấy, việc phân tách danh sách mơ tả quan hệ ghép append
Do ta viết lại Prolog sau : sublist( S, L ) :
-append( L1, L2, L ), -append( S, L3, L2 ).
Ta thấy thủ tục sublist mềm dẻo sử dụng theo nhiều cách khác Chẳng hạn ta liệt kê danh sách danh sách cho sau :
?- sublist( S, [ a, b, c ] ) S = [ ];
S = [ a ]; S = [ a, b ]; S = [ a, b, c ]; S = [ b ];
III.2.Hốn vị
Đơi khi, ta cần tạo hoán vị danh sách Ta xây dựng quan hệ permutation có hai tham biến hai danh sách, mà danh sách hoán vị danh sách Ta tận dụng phép quay lui sau :
?- permutation( [ a, b, c ], P ) P = [ a, b, c ];
P = [ a, c, b ];
L
X L2
L1
[ X | L2 ] L
L2
L1 S L3
member( X, L )
(13)Cấu trúc danh sách 107 P = [ b, a, c ];
Nguyên lý hoạt động thủ tục swap dựa hai trường hợp phân biệt, tuỳ theo danh sách thứ :
1 Nếu danh sách thứ rỗng, danh sách thứ hai phải rỗng
2 Nếu danh sách thứ khác rỗng, có dạng [ X | L ]và tiến hành hoán vị sau : trước tiên hoán vị L để nhận L1, sau chèn X vào tất vị trí L1
Hình III.4 Một cách xây dựng hoán vịpermutation danh sách [ X | L ]
Ta nhận hai mệnh đề tương ứng với thủ tục sau : permutation( [ ], [ ] ).
permutation( [ X | L ], P ) :
-permutation( L, L1 ), insert( X, L1, P ).
Một phương pháp khác loại bỏ phần tử X khỏi danh sách đầu tiên, hoán vị phần lại danh sách để nhận danh sách P, sau thêm X vào phần đầu P Ta có chương trình khác permutation2 sau :
permutation2( [ ], [ ] ).
permutation2( L, [ X | P ] ) :
-remove( X, L, L1 ), permutation2( L1, P ).
Từ đây, ta khai thác thủ tục hốn vị, chẳng hạn (chú ý chạy Arity Prolog cần gõ vào dấu chấm phẩy ; sau ->) :
?- permutation( [ red, blue, green ], P ) P = [ red, blue, green ];
P = [ red, green, blue ]; P = [ blue, red, green ]; P = [ blue, green, red ]; P = [ green, red, blue ]; P = [ green, blue, red ]; Yes
Hoặc sử dụng permutation theo cách khác sau : L1 hoán vị L
Chèn X vị tríđể nhận hoán vị [ X | L ]
X L
(14)Lập trình lơgic Prolog 108 ?- permutation( L, [ a, b, c ] )
Prolog ràng buộc liên tiếp cho L để đưa hốn vị khác Tuy nhiên, NSD yêu cầu giải pháp khác, Prolog không trả lời “No”, mà rơi vào vịng lặp vơ hạn phải tìm kiếm hốn vị mà thực khơng tồn Trong trường hợp này, thủ tục permutation2 tìm thấy hốn vị thứ nhất, sau rơi vào vịng lặp vơ hạn Vì vậy, cần ý sử dụng quan hệ hoán vị
III.3.Một số ví dụ danh sách
III.3.1. Sắp xếp phần tử danh sách
Xây dựng thủ tục xếp phần tử có danh sách phương pháp chèn sau :
ins(X, [ ], [ X ])
ins(X, [H|T], [ X,H|T ]) :- X @=< H
ins(X, [ H|T ], [ H|L ]) :- X @> H, ins( X, T, L )
?- ins(8, [ 1, 2, 3, 4, ], L) L = [1, 2, 3, 4, 5, 8]
Yes
?- ins(1, L, [ 1, 2, 3, 4, ]) L = [2, 3, 4, 5]
Yes
ins_sort([ ], [ ]) ins_sort([H|T], L) :-
ins_sort(T, L1), ins(H, L1, L)
?- ins_sort([3, 2, 6, 4, 7, 1], L) L = [1, 2, 3, 4, 6, 7]
Yes
III.3.2. Tính độ dài danh sách
Xây dựng thủ tục tính độ dài hay đếm số lượng phần tử có mặt danh sách cho sau :
(15)Cấu trúc danh sách 109 Nếu danh sách rỗng, độ dài N =
2 Nếu danh sách khác rỗng, tạo thành từ danh sách có dạng : [ head | queue ]
và có độ dài cộng với độ dài queue Ta có chương trình Prolog sau :
length( [ ], ).
length( [ _ | Queue ], N ) :- length(Queue, N1 ), N is + N1.
Kết chạy Prolog sau :
?- length( [ a, b, c, d, e ], N ). N =
Yes
?- length( [ a, [ b, c ], d, e ], N ). N =
Yes
Ta thấy mệnh đề thứ hai, hai đích phần thân khơng thể hốn đổi cho nhau, N1 phải ràng buộc trước thực đích :
N is + N1
Chẳng hạn, gọi trace, trình thực length( [ 1, 2, ], N ) sau :
(0) gọi length([1, 2, 3], N) -> (1) gọi length([2, 3], N’) -> (2) gọi length([3], N’’) ->
(3) gọi length([ ], N’’’) -> N’’’ = (4) gọi N’’ is + -> N’’ =
(5) gọi N’ is + -> N’ = (6) gọi N is + -> N =
Với is, ta đưa vào quan hệ nhạy cảm với thứ tự thực đích, khơng thể bỏ qua yếu tố thủ tục chương trình
Điều xảy ta không sử dụng is chương trình Chẳng hạn : length1( [ ], ).
length1( [ _ | Queue ], N ) :- length1( Queue, N1 ), N = + N1.
Lúc này, gọi :
(16)Lập trình lơgic Prolog 110 Prolog trả lời :
N = + (1 + (1 + (1 + 0))) Yes
Phép cộng không khởi động cách tường minh nên không thực Tuy nhiên, ta hốn đổi hai đích mệnh đề thứ hai length1 :
length1( [ ], )
length1( [ _ | Queue ], N ) :- N = + N1,
length1( Queue, N1 ).
Kết chạy chương trình sau hốn đổi y hệt cũ Bây giờ, ta lại rút gọn mệnh đề cịn đích :
length1( [ ], )
length2( [ _ | Queue ], + N ) :- length2( Queue, N ).
Kết chạy chương trình lần y hệt cũ Prolog không đưa trả lời mong muốn, mà :
?- length1([ a, b, c, d], N) N = 1+ (1+ (1+ (1+0)))
Yes
III.3.3. Tạo sinh số tự nhiên
Chương trình sau tạo sinh liệt kê số tự nhiên : % Natural Numbers
nat(0)
nat(N) :- nat(M), N is M + Khi thực đích câu hỏi : ?- nat(N), write(N), nl, fail
(17)Cấu trúc danh sách 111
Tóm tắt chương
• Danh sách cấu trúc rỗng, gồm hai phần : phần đầu phần tử phần cịn lại danh sách
• Prolog quản lý danh sách theo cấu trúc nhị phân Prolog cho phép sử dụng nhiều cách khác để biểu diễn danh sách
[ Object1, Object2, ] [ Head | Tail ]
hoặc [ Object1, Object2, | Others ] Với Tail Others danh sách
• Các thao tác cổ điển danh sách lập trình : kiểm tra phần tử có thuộc danh sách cho trước không, phép ghép hai danh sách, bổ sung loại bỏ phần tử đầu cuối danh sách, trích danh sách
Bài tập chương
1 Viết thủ tục sử dụng append để xóa ba phần tử cuối danh sách L, tạo danh sách L1 Hướng dẫn : L phép ghép L1 với danh sách ba phần tử (đã bị xóa khỏi L)
2 Viết dãy đích để xóa ba phần tử ba phần tử cuối danh sách L, để trả danh sách L2
3 Định nghĩa quan hệ :
last_element( Object, List )
sao cho Object phải phần tử cuối danh sách List Hãy viết thành hai mệnh đề, có mệnh đề sử dụng append, mệnh đề không sử dụng append
4 Định nghĩa hai vị từ :
even_length( List )và odd_length( List )
được thõa mãn số phân tử danh sách List chẵn hay lẻ tương ứng Ví dụ danh sách :
[ a, b, c, d ] có độ dài chẵn, [ a, b, c ] có độ dài lẽ
5 Cho biết kết Prolog trả lời câu hỏi sau : ?- [1,2,3] = [1|X]
(18)Lập trình lơgic Prolog 112
?- [1 | [2,3]] = [1,2,X] ?- [1 | [2,3,4]] = [1,2,X] ?- [1 | [2,3,4]] = [1,2|X] ?- b(o,n,j,o,u,r) = L ?- bon(Y) = [X,jour] ?- X(Y) = [bon,jour]
6 Viết chương trình Prolog kiểm tra danh sách có phải tập hợp danh sách khác khơng ? Chương trình hoạt động sau :
?- subset2([4,3],[2,3,5,4]) Yes
7 Viết chương trình Prolog để lấy phần tử từ danh sách Chương trình chèn phần tử vào danh sách hoạt động sau : ?- takeout(3,[1,2,3],[1,2])
Yes
?- takeout(X,[1,2,3],L) X =
L = [2, 3] ; X =
L = [1, 3] ; X =
L = [1, 2] ; No
?- takeout(4,L,[1,2,3])
L = [4, 1, 2, 3] ; L = [1, 4, 2, 3] ; L = [1, 2, 4, 3] ; L = [1, 2, 3, 4] ; No
8 Viết vị từ Prolog getEltFromList(L,N,E) cho phép lấy phần tử thứ N danh sách Thất bại danh sách khơng có đủ N phần tử Chương trình hoạt động sau :
?- getEltFromList([a,b,c],0,X) No
?- getEltFromList([a,b,c],2,X) X = b
(19)Cấu trúc danh sách 113 Viết chương trình Prolog tìm phần tử lớn phần tử nhỏ
danh sách số Chương trình hoạt động sau : ?- maxmin([3,1,5,2,7,3],Max,Min) Max =
Min = Yes
?- maxmin([2],Max,Min) Max =
Min = Yes
10 Viết chương trình Prolog chuyển danh sách phức hợp, danh sách mà phần tử danh sách chứa danh sách phức hợp khác, thành danh sách phẳng danh sách chứa phần tử tất danh sách có thể, giữ nguyên thứ tự lúc đầu Chương trình hoạt động sau :
flatten([[1,2,3],[4,5,6]], Flatlist) Flatlist = [1,2,3,4,5,6]
Yes
flatten([[1,[hallo,[[aloha]]],2,[],3],[4,[],5,6]], Flatlist)
Flatlist = [1, hallo, aloha, 2, 3, 4, 5, 6] Yes
11 Viết chương trình Prolog thực vị từ xử lý tập hợp cho phần lý thuyết (mục II)
12 Sử dụng vị từ forall để viết chương trình Prolog kiểm tra hai danh sách có rời (disjoint) khơng ? Chương trình hoạt động sau :
?- disjoint([a,b,c],[d,g,f,h]) Yes
?- disjoint([a,b,c],[f,a]) No
13 Vị từ forall(Cond, Action) thực kiểm tra so khớp tương ứng Cond, thường kết hợp với vị từ member, Action Ví dụ kiểm tra việc thực phép toán số học danh sách L đắn ?- forall(member(Result = Formula, [2 = + 1, = * 2]), Result =:= Formula)
(20)Lập trình lôgic Prolog 114 14 Sử dụng vị từ forall để viết chương trình Prolog kiểm tra danh sách
có tập hợp danh sách khác hay khơng ? Chương trình hoạt động sau :
?- subset3([a,b,c],[c,d,a,b,f]) Yes
?- subset3([a,b,q,c],[d,a,c,b,f]) No
15 Sử dụng vị từ append ghép hai danh sách để viết chương trình Prolog thực việc sau :
prefixe(L1, L2) danh sách L1 đứng trước (prefixe list) danh sách L2 suffixe(L1, L2) danh sách L1 đứng sau (suffixe list) danh sách L2 isin(L1, L2) phần tử danh sách L1 có mặt danh sách L2 16 Sử dụng phương pháp Quicksort viết chương trình Prolog xếp nhanh
danh sách số cho theo thứ tự tăng dần
17 Đọc hiểu chương trình sau dựng lại thuật toán : /* Missionarys & Cannibals */
/* Tránh vòng lặp */ lNotExist(_,[])
lNotExist(X,[T|Q]) :-
X\==T, lNotExist(X,Q) /* Kiểm tra tính hợp lý trạng thái */ valid(MG,CG,MD,CD) :-
MG>=0, CG>=0, MD>=0, CD>=0, MG=0, MD>=CD valid(MG,CG,MD,CD) :-
MG>=0, CG>=0, MD>=0, CD>=0, MG>=CG, MD=0 valid(MG,CG,MD,CD) :-
MG>=0, CG>=0, MD>=0, CD>=0, MG>=CG, MD>=CD /* Xây dựng cung kiểm tra */
sail(1,0) sail(0,1) sail(1,1) sail(2,0) sail(0,2) arc([left,MGi,CGi,MDi,CDi],[droite,MGf,CGf,MDf,CDf]) :-
sail(Mis,Can),
MGf is MGi-Mis, MDf is MDi+Mis, CGf is CGi-Can, CDf is CDi+Can, valid(MGf,CGf,MDf,CDf)
arc([right,MGi,CGi,MDi,CDi],[left,MGf,CGf,MDf,CDf]) :- sail(Mis,Can),
MGf is MGi+Mis, MDf is MDi-Mis, CGf is CGi+Can, CDf is CDi-Can, valid(MGf,CGf,MDf,CDf)