Tải bản đầy đủ (.docx) (20 trang)

Lập trình hàm tìm hiểu và áp dụng ngôn ngữ lisp giải bài toán “tháp hà nội”

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (194.77 KB, 20 trang )

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN
────────*────────
TIỂU LUẬN MÔN HỌC
NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH
ĐỀ TÀI: LẬP TRÌNH HÀM
TÌM HIỂU VÀ ÁP DỤNG NGÔN NGỮ LISP
GIẢI BÀI TOÁN “THÁP HÀ NỘI”
Nhóm thực hiện : Tạ Xuân Thọ
Nguyễn Trần Minh
Hà Minh Đức
Lớp : 13BCNNT1
Giáo viên hướng dẫn : TS. Cao Tuấn Dũng
Hà Nội 12/2013
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
MỤC LỤC
MỞ ĐẦU
Theo thống kê đã có khoảng 2500 ngôn ngữ lập trình được phát triển và
sử dụng trên toàn thế giới. Hầu hết các ngôn ngữ lập trình được thiết kế dựa
trên nguyên lý Von Newmann, phụ thuộc rất nhiều vào phần cứng của máy tính,
đó là các ngôn ngữ lập trình mệnh lệnh. Do sự phổ biến, sự ứng dụng mạnh mẽ
vào việc phát triển các ứng dụng thương mại và sự chấp nhận rộng rãi ngôn
ngữ lập trình mệnh lệnh nên phần lớn các lập trình viên chỉ nghe qua và thậm
Nhóm 11
2
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
chí không biết đến các ngôn ngữ lập trình dạng khác, như lập trình logic hay
lập trình hàm.
Nhằm mục đích hiểu sâu hơn về môn học “Nguyên lý các ngôn ngữ lập
trình” và cũng là cơ hội tìm hiểu, tiếp cận cách tư duy khác về lập trình, tiểu
luận Nhóm 11 sẽ trình bày các tìm hiểu và áp dụng ngôn ngữ lập trình hàm vào


bài toán thực tế. Cụ thể, tiểu luận sẽ trình bày các khái niệm, đặc trưng của
ngôn ngữ lập trình hàm, qua đó chọn ngôn ngữ Lisp làm đối tượng tiếp cận và
áp dụng giải bài toán “Tháp Hà Nội” trên đặc trưng của ngôn ngữ.
Do thời gian tìm hiểu còn ngắn, kinh nghiệm và kiến thức trình bày còn
hạn chế nên trong tiểu luận sẽ còn các vấn đề chưa được chuyên sâu, chủ yếu
mang tính nắm bắt và ứng dụng lập trình hàm để thấy được sự khác nhau về tư
duy và cách xây đựng ứng dụng khác biệt với lập trình mệnh lệnh.
Để hoàn thành tiểu luận, Nhóm 11 đã được sự hướng dẫn của TS. Cao
Tuấn Dũng thông qua môn học “Nguyên lý các ngôn ngữ lập trình”. Nhóm
chúng em xin chân thành cảm ơn Thầy và rất mong nhận được các đóng góp,
nhận xét của Thầy cũng như các bạn cùng lớp để chúng em có được sự tiếp cận
sâu hơn, đầy đủ hơn về ngôn ngữ lập trình hàm nói chung và ngôn ngữ lập trình
Lisp nói riêng.
DANH MỤC TỪ VIẾT TẮT
STT Từ gốc Viết tắt Ghi chú
1 Common Lisp CL
2 Ngôn ngữ lập trình NNLT
3 Lập trình hàm LTH
4 Lập trình mệnh lệnh LTML
Nhóm 11
3
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
1. TỔNG QUAN VỀ LẬP TRÌNH HÀM
Trong phần tổng quan về lập trình hàm, tiểu luận sẽ trình bày sơ lược về
các khái niệm và đặc trưng của lập trình hàm, từ đó cho thấy sự khác biệt về cả
tư duy và cách xây dựng ứng dụng so với lập trình mệnh lệnh.
1.1. Tổng quan
Đã có rất nhiều các ngôn ngữ lập trình ra đời cùng với quá trình phát triển
của công nghệ thông tin, công nghệ phần mềm. Các ngôn ngữ lập trình đã được
áp dụng phù hợp tùy theo lĩnh vực cần xử lý, tùy theo bài toán và cả điểm mạnh

yếu của ngôn ngữ đó. Tuy nhiên, hầu hết các ngôn ngữ từ trước đến nay là ngôn
Nhóm 11
4
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
ngữ lập trình mệnh lệnh, dựa trên nguyên lý kiến trúc máy tính Von Newmann
và được phát triển rộng rãi, như C, C++, Pascal… Mặc dù phát triển và được hầu
hết lập trình viên chấp nhận, nhưng ngôn ngữ lập trình mệnh lệnh là một sự hạn
chế trong việc phát triển phần mềm do quá phụ thuộc vào kiến trúc máy tính.
Ngược lại, ngôn ngữ lập trình hàm ra đời và phát triển dựa trên các hàm toán
học, là dạng ngôn ngữ không ra lệnh.
Lập trình hàm xuất phát từ phép tính labada, một hệ thống hình thức được
phát triển để nghiên cứu định nghĩa hàm số, ứng dụng của hàm số và đệ quy.
Lập trình hàm là một mô hình lập trình xem việc tính toán là sự đánh giá các
hàm toán học, tránh sử dụng các trạng thái và các kiểu dữ liệu biến đổi, nhấn
mạnh vào việc ứng dụng các hàm sô, trái ngược với lập trình mệnh lệnh nhấn
mạnh vào sự thay đổi trạng thái.
Trong thực tế, sự khác biệt giữa hàm số toán học và hàm trong lập trình
mệnh lệnh đó là hiệu ứng lề: là hiệu ứng làm thay đổi giá trị phép tính trước đó.
Điều đó có nghĩa là cùng một biểu thức ngôn ngữ lại có thể tạo ra nhiều giá trị
khác nhau vào các thời điểm khác nhau tùy vào trạng thái của chương trình đang
thực thi. Trái lại, trong lập trình hàm, giá trị xuất ra của một hàm chỉ phụ thuộc
các tham số đầu vào của hàm, do đó gọi hàm nhiều lần với cùng tham số sẽ cho
kết quả như nhau. Loại bỏ hiệu ứng lề khiến chương trình dễ hiểu và dễ dự đoán
được hành vi của chương trình, đó cũng là điểm mạnh cho sự phát triển của lập
trình hàm.
Hiện nay, các ngôn ngữ lập trình hàm chủ yếu ảnh hưởng đến giới nghiên
cứu nhiều hơn là thương mại vì nhiều lý do. Một số các ngôn ngữ nổi bật như
Haskell, Sheme, Lisp… mà trong phần sau chủ yếu sẽ trình bày về ngôn ngữ
Lisp.
1.2. Hàm toán học

Trong toán học, hàm là một quy tắc tương ứng (ánh xạ) các phần tử của
miền xác định với các phần tử của miền giá trị, thường mô tả bằng một biểu
thức. Các hàm toán học có đặc trưng:
- Không có hiệu ứng lề nên cùng một tập đối số sẽ luôn cho cùng một
kết quả.
- Thứ tự đánh giá biểu thức dựa vào điều kiện và đệ quy.
Nhóm 11
5
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
Định nghĩa hàm thường là tên hàm, danh sách tham số và sau đó là biểu
thức, khi áp dụng một phần tử cụ thể của miền xác định (đối số) sẽ thay thế cho
tham số trong định nghĩa hàm.
Tên hàm + danh sách tham số = biểu thức
Ví dụ: binh_phuong(x) = x*x
1.3. Biểu thức labada
Đôi khi trong biểu diễn người ta sử dụng hàm không tên, khi đó người ta
sử dụng biểu thức labada. Ví dụ λ(x)x*x thay thế hàm binh_phuong(x) = x*x.
Tham số biểu thức lambda gọi là tham số biến kết ghép, khi biểu thức
lambda được định trị đối với một tham số đã cho chính là biểu thức được áp
dụng cho tham số đó (λ(x) x * x)(10) có giá trị là 100.
1.4. Dạng hàm
Dạng hàm là sự tổ hợp của các hàm: một hàm (chương trình) được xây
dựng từ các hàm sẵn có bằng cách sử dụng các tác vụ tạo chương trình.
1.4.1. Hàm hợp
- Hàm hợp là hàm dùng 2 hàm như là 2 tham số và dùng kết quả của
hàm đầu tiên như là tham số thực cho hàm thứ 2
- Ví dụ: Hàm hợp h = f. g hay h(x) = f ( g ( x))
Nếu f (x) = x + 1 và g (x) = 2*x thì h = f.g = (2 * x)+ 1
1.4.2. Hàm xây dựng
- Hàm xây dụng là hàm mà các tham số của chúng cũng là hàm, ký hiệu

hàm xây dựng: []
- Khi áp dụng vào một đối số vào hàm xây dựng: đối số được áp dụng
vào hàm tham số →tập hợp kết quả.
- Ví dụ: f(x) = x, g(x) = x+1 và h(x) = x*x*x thì [f,g,h](4) có kết quả là
(4,5,64)
1.4.3. Hàm áp dụng cho tất cả
- Là hàm lấy một hàm đơn như là một tham số, ký hiệu là α
- Áp dụng hàm tham số vào danh sách các đối số, ta được một danh sách
kết quả.
- Ví dụ: f(x) = x * x thì α( f, (1,2)) kết quả (1, 4)
Nhóm 11
6
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
1.5. Bản chất ngôn ngữ lập trình hàm
Mục đích của việc thiết kế ngôn ngữ lập trình hàm là mô phỏng các hàm
toán học một cách nhiều nhất có thể được. Tiến trình tính toán trong ngôn ngữ
lập trình hàm khác ngôn ngữ lập trình mệnh lệnh:
- Trong lập trình mệnh lệnh, biểu thức được định giá và kết quả của nó
được lưu trữ trong ô nhớ. Quản lý biến là một trong những nhân tố làm
phức tạp thiết kế ngôn ngữ lập trình, trong ngôn ngữ lập trình hàm biến
là không cần thiết.
- Các lệnh lặp lại sẽ được xử lý bằng đệ qui.
- Chương trình là các định nghĩa hàm và các áp dụng hàm, sự thực hiện
là việc đánh giá các áp dụng hàm, sự thực hiện một hàm luôn cho cùng
một kết quả khi ta cho nó cùng một đối số.Ví dụ: f(x) + f(x) và 2 * f(x)
luôn luôn cùng kết quả
- Ngữ nghĩa của ngôn ngữ lập trình hàm đơn giản hơn ngữ nghĩa của
ngôn ngữ lập trình mệnh lệnh.
2. NGÔN NGỮ LISP
Lisp - viết tắt bởi từ LIST PROCESSING - phát triển từ rất sớm (1958),

được biết đến như một trong những ngôn ngữ lập trình hàm tiêu biểu. Trong
phần này sẽ giới thiệu sơ lược về lịch sử của ngôn ngữ Lisp, các đặc điểm và cú
pháp lập trình.
2.1. Giới thiệu về Lisp
Được John McCarthay đề xuất vào năm 1958, Lisp là một trong số các
ngôn ngữ lập trình ra đời sớm nhất. Trình biên dịch đầu tiên được viết bởi Tim
Hart và Mike Levin vào năm 1962, bằng chính ngôn ngữ Lisp. Đến những năm
80, nhờ được ứng dụng trong lĩnh vựa trí tuệ nhân tạo nên Lisp trở nên phát triển
mạnh mẽ, có đến 12 hệ Lisp khác nhau và các hệ Lisp này không tương thích
với nhau.
Một dự án kết hợp các đặc tính tốt nhất của các hệ Lisp vào một phiên bản
thống nhất đã hình thành và cho ra đời phiên bản đầu tiên vào năm 1984, đó
chính là Common Lisp (CL). Phiên bản này đã kết hợp được nhiều ý tưởng của
ngôn ngữ lập trình như thông dịch, dọn rác, gọi hàm đệ quy, theo vết, dò lỗi và
trình soạn thảo theo cú pháp.
Nhóm 11
7
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
Đặc điểm cơ bản của Lisp:
- Lisp là ngôn ngữ thông dịch.
- Lisp dùng lối ký hiệu tiền tố và các dấu ngoặc đơn.
Lisp phát triển mạnh mẽ nhờ các ưu điểm:
- Cú pháp đơn giản, cấu trúc duy nhất trong Lisp là danh sách: tất cả các
hàm đều được viết dưới dạng danh sách và không có lệnh hay từ khóa.
- Là một ngôn ngữ mạnh nhờ tính tương đương giữa dữ liệu và chương
trình, lý do chúng đều là danh sách nên có thể thao tác nhờ chung một
công cụ.
- Mềm dẻo, dễ phát triển, tương đối dễ học.
Trong phần tiếp theo của tiểu luận, các đặc điểm và cú pháp của Lisp sẽ
được trình bày theo phiên bản Common Lisp.

2.2. Các khái niệm cơ bản
Cũng như các ngôn ngữ lập trình khác, chương trình viết bằng ngôn ngữ
Lisp được xây dựng nên từ các đối tượng cơ bản:
2.2.1. Nguyên tử (atom)
Nguyên tử là khái niệm cơ bản của Lisp, nguyên tử có thể là số
hoặc ký hiệu.
- Số: biểu diễn như trong các ngôn ngữ lập trình khác, ví dụ 1,-2, 1/3,…
- Ký hiệu: là một chuỗi các ký tự , trừ các ký tự đặc biệt, dấu ngoặc đơn
và khoảng trống. Các hằng ký hiệu được biểu diển bởi một dấu nháy
đơn đằng trước (ví dụ ‘A, ‘Anh), các hằng ký hiệu số được xem như là
số (ví dụ ‘2 = 2) và một số hằng ký hiệu được định nghĩa trước ( như T
= True, NIL = null…).
2.2.2. Danh sách (list)
Danh sách là kiểu cấu trúc duy nhất trong Lisp, là một dãy các phần
tử ngăn cách nhau bởi khoảng trắng, có phân biệt thứ tự và đặt trong cặp
ngoặc đơn. Hằng danh sách được bắt đầu bởi dấu nháy đơn đằng trước,
phần tử của danh sách có thể là nguyên tử hoặc danh sách khác.
Ví dụ:
Nhóm 11
8
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
- Hằng danh sách: ‘(1 2 3), ‘(a b c)…
- Danh sách là phần tử: ‘(a (b c) d e)…
2.2.3. Biểu thức
Biểu thức luôn có một giá trị, có thể là một nguyên tử hoặc một
danh sách. Cách xác định giá trị biểu thức như sau:
- Nếu biểu thức là một số thì giá trị biểu thức là giá trị của số đó.
- Nếu biểu thức là một ký hiệu thì giá trị của biểu thức sẽ được xác định
bởi Lisp (như T có giá trị TRUE) hoặc giá trị người dùng gán cho biến
được xác định bởi ký hiệu đó. Ví dụ:

> (setq x 10) ; gán giá trị 10 ký hiệu (biến) x
> x ; truy vấn giá trị ký hiệu “x”
> 10 ; giá trị ký hiệu “x” được xác định là 10
- Nếu biểu thức là một danh sách dạng (E
0
, E
1
, … E
n
) thì giá trị được xác
định: E
0
phải là một hàm đã được Lisp nhận biết; các phần tử E
1
, … E
n
được định trị từ trái qua phải với các giá trị tương ứng V
1
, … V
n
; hàm
E
0
áp dụng cho V
1
, … V
n
và giá trị biểu thức chính là giá trị hàm E
0
. Ví

dụ:
> (+ 1 2 3) ; tính giá trị biểu thức
> 6 ; giá trị hàm + là 6
2.3. Hàm trên Lisp
Chương trình trên Lisp là một hàm hoặc hàm hợp, có thể là các hàm đã
được xây dựng sẵn trong ngôn ngữ hoặc hàm do người dùng định nghĩa. Trong
phần này tiểu luận sẽ giới thiệu một số hàm cơ bản của Lisp và cách xây dựng
hàm do người dùng định nghĩa.
2.3.1. Hàm số học
Hàm số học tác động lên các biểu thức số học, kết quả trả về là một
số. Các hàm số học được xây dựng sẵn trong ngôn ngữ Lisp:
- Hàm +,-,*,/: thực hiện phép toán cộng, trừ, nhân, chia trên các số:
> (+ 1 2 3) = 6 ; phép toán cộng
> (- (+ 1 2) 3) = 0 ; phép toán trừ
- Hàm MOD, SQRT: thực hiện phép chia lấy dư, phép bình phương:
> (MOD 10 3) = 1 ; phép chia lấy dư
> (SQRT 10) = 100 ; phép bình phương
Nhóm 11
9
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
2.3.2. Hàm so sánh
Hàm so sánh xây dựng sẵn có nhiều loại: so sánh giá trị số, so sánh
ký hiệu hoặc so sánh hai đối tượng bất kỳ:
- Hàm >, <, >=, <=, =, /=: thực hiện phép so sánh giá trị trên các số, kết
quả là T (True) hoặc NIL (trong Lisp không có giá trị False):
> (> 1 2) = NIL ; so sánh 1 >2, kết quả NIL
> (< 1 2) = T ; so sánh 1 >2, kết quả T
- Hàm EQ: so sánh hai ký hiệu:
> (EQ ‘a ‘ab) = NIL ; so sánh ký hiệu ‘a và ‘ab
- Hàm EQUAL: so sánh hai đối tượng bất kỳ:

> (EQUAL ‘(a b) ‘(a b)) = T ; so sánh hai danh sách
2.3.3. Hàm logic
Tương tự các ngôn ngữ lập trình khác, Lisp cũng có các hàm logic
thông dụng tuy nhiên cách xác định giá trị biểu thức logic thì khác biệt
đôi chút:
- Hàm AND: (AND E
0
, E
1
, … E
n
) nhận vào các biểu thức E
0
, E
1
, … E
n
sau đó định trị từ trái qua phải, nếu E
k
= NIL thì hàm trả về NIL, nếu
không hàm sẽ trả lại giá trị E
n
.
> (AND (- 1 3) (+ 1 2)) = 3 ; tất cả các hàm đều không NIL
- Hàm OR: (OR E
0
, E
1
, … E
n

) nhận vào các biểu thức E
0
, E
1
, … E
n
sau
đó định trị từ trái qua phải, nếu E
k
<> NIL thì hàm trả về giá trị biểu
thức đó và dừng tính toán, nếu không hàm sẽ trả lại giá trị NIL;
> (OR (- 1 3) (+ 1 2)) = -2 ; biểu thức đầu tiên không NIL
- Hàm NOT: (NOT E) nhận biểu thức E, nếu E <> NIL thì trả về NIL và
ngược lại trả về T.
> (NOT (>1 2)) = T ; biểu thức NIL, trả lại T
2.3.4. Hàm thao tác trên danh sách
Do xử lý chủ yếu trên các danh sách nên trong Lisp định nghĩa sẵn
một số hàm cơ bản để thao tác với danh sách thuận tiện hơn:
- Hàm CAR: (CAR L) nhận vào danh sách L và trả về phần tử đầu tiên
của danh sách. Trình thông dịch sẽ báo lỗi nếu L không phải là danh
sách:
> (CAR ‘(1 2 3)) = 1 ; trả lại 1 là phần tử đầu tiên
> (CAR NIL) = NIL ; trả lại NIL với danh sách rỗng
Nhóm 11
10
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
> (CAR 1) = error ; lỗi khi không phải là danh sách
- Hàm CDR: (CDR L) nhận vào sách sách L và trả về phần còn lại của
danh sách trừ phần tử đầu tiên. Tương tự CAR, khi sử dụng CDR sẽ
nhận được báo lỗi nếu L không phải là danh sách:

> (CDR ‘(1 2 3)) = (2 3) ; trả lại phần còn lại của danh sách
> (CDR NIL) = NIL ; trả lại NIL với danh sách rỗng
> (CDR 1) = error ; lỗi khi không phải là danh sách
- Hàm CONS: (CONS x L) nhận vào phần tử x, danh sách L và thêm x
vào đầu danh sách L:
> (CONS 1 ‘(1 2 3)) = (1 1 2 3) ; thêm x vào đầu danh sách
> (CONS 1 NIL) = (1) ; thêm x vào danh sách rỗng
- Hàm LIST: (LIST E
0
, E
1
, … E
n
) nhận vào các biểu thức E
0
, E
1
, … E
n
,
trả lại danh sách V
0
, V
1
, … V
n
là các giá trị tương ứng.
> (LIST (+ 1 2) (-3 1)) = (3 2) ; trả lại danh sách giá trị
- Hàm LENGTH: (LENGTH L) trả lại độ dài danh sách:
> (LENGTH NIL) = 0 ; danh sách rỗng

> (LENGTH ‘(1 2 3)) = 3 ; danh sách có 3 phần tử
- Hàm REVERSE: (REVERSE L) đảo ngược danh sách:
> (REVERSE NIL) = NIL ; danh sách rỗng
> (REVERSE ‘(1 2 3)) = (3 2 1) ; danh sách có 3 phần tử
- Hàm APPEND: (APPEND L1 L2) kết hợp các danh sách:
> (APPEND ‘(1 2) ‘(2 4) = (1 2 3 4) ;kết hợp 02 danh sách
2.3.5. Hàm điều khiển
Lisp hỗ trợ một số hàm điều khiển IF, COND, PROG1 và PROGN:
a. Hàm IF:
- Hàm (IF E
1
E
2
E
3
) nhận vào 3 biểu thức E
1
E
2
E
3
, kết quả trả về nếu E
1
khác NIL, ngược lại là E
3
.
- Hàm có thể viết gọn (IF E
1
E
2

) tường đương với (IF E
1
E
2
NIL).
- Nếu E
2
khác NIL thì (IF E
1
E
2
E
3
) tương đương (OR (AND E
1
E
2
) E
3
),
do đó có thể hiểu OR và AND cũng là các hàm điều khiển.
> (IF (+ 1 3) 1 3) = 1 ; trả lại 1 do (+1 3) khác NIL
> (IF (> 1 3) 1 3) = 3 ; trả lại 1 do (>1 3) = NIL
b. Hàm COND:
- Hàm COND điều khiển việc lựa chọn, có dạng:
(COND
(ĐK
1
E
1

)
(ĐK
2
E
2
)
(ĐK
3
E
3
)
Nhóm 11
11
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”

[(T E
n+1
)]
)
- Hàm sẽ dừng và trả lại giá trị E
k
nếu ĐK
k
khác NIL. Nếu tất cả các ĐK
đều NIL sẽ trả lại NIL hoặc giá trị E
n+1
nếu có khai báo.
c. Hàm PROG1:
- Hàm (PROG1 E
1

E
2
E
n
) nhận vào các biểu thức E
1
E
2
E
n
, tính giá trị
bảo biểu thức từ trái qua phải và trả về giá trị biểu thức E
1
.
> (PROG1 (+ 1 2) (+ 2 3)) = 3 ; trả lại giá trị biểu thức đầu tiên
d. Hàm PROGN:
- Hàm (PROGN E
1
E
2
E
n
) nhận vào các biểu thức E
1
E
2
E
n
, tính giá trị
bảo biểu thức từ trái qua phải và trả về giá trị biểu thức E

n
.
> (PROGN (+ 1 2) (+ 2 3)) = 5 ; trả lại giá trị biểu thức cuối cùng
2.3.6. Hàm xuất/nhập
Một số hàm xuất nhập cơ bản như đọc dữ liệu từ bàn phím, xuất giá trị ra
màn hình là các hàm thường dùng:
- Hàm READ: (READ) đọc dữ liệu từ bàn phím, kết thúc khi nhấn phìm
Enter và trả lại dữ liệu đã được nhập.
- Hàm PRINT: (PRINT E) xuất dữ liệu biểu thức E ra màn hình, xuống
dòng và trả lại giá trị biểu thức E.
- Hàm PRINC: (PRINC E) xuất dữ liệu biểu thức E ra màn hình trên
cùng một dòng và trả lại giá trị biểu thức E.
- Hàm FORMAT: (FORMAT T “~A~%” E) sử dụng xuất dữ liệu biểu
thức E ra màn hình, trong đó ~A tương ứng vị trì biểu thức E, ~% nếu
định dạng xuống dòng.
2.3.7. Biến số
Trong lập trình hàm, việc sử dụng biến số nên hạn chế tối đa và nếu cần
thiết thì nên sử dụng các biến số cụ bộ.
Biến toàn cục: là biến mà phạm vi của nó có thể sử dụng trong tất cả các
hàm, biến được giải phòng khi chương trình kết thúc.
Biến cục bộ: là biến mà phạm vi của nó chỉ nằm trong hàm tạo ra nó vf sẽ
tự giải phóng khi hàm kết thúc.
Một số các hàm khai báo biến và cách khai báo biến cục bộ:
Nhóm 11
12
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
- Hàm SETQ: (SETQ x E): khai báo biến x, gán giá trị bằng biểu thức E.
Biến khai báo trở thành biến toàn cục, không mất đi sau khi hàm thực
hiện xong.
> (SETQ x 100) ; khai báo biến x, gán cho biến giá trị 100

- Hàm DEFVAR: (DEFVAR x [E]) khai báo biến x và gán giá trị bằng
biểu thức E hoặc không.
> (DEFVAR x (+ 1 2)) ; khai báo biến toàn cục x giá trị 3
> (DEFVAR y) ; khai báo biến toàn cục y
- Hàm LET: (LET ((V
1
E
1
) (V
2
E
2
) … (V
k
E
k
)) E
k+1
E
n
) gán giá trị cho các
biến V
i
bởi giá trị biểu thức

E
i
, sau đó định trị các biểu thức E
k+1,…
E

n
từ
trái qua phải, trả lại E
n
. Để sử dụng V
i
như biến cục bộ, trong phần
định trị biểu thức sử dụng SETQ để gán giá trị cho biến vừa tạo ra.
2.3.8. Đệ quy
Cũng như các ngôn ngữ lập trình khác, Lisp xử lý các hàm đệ quy và xử
lý rất mạnh. Đệ quy là hàm có lời gọi đến chính nó trong biểu thức định nghĩa
hàm. Hàm đệ quy có hai đặc tính:
- Phải có ít nhất một trường hợp dừng để có thể kết thúc lời gọi đệ quy.
- Lời gọi đệ quy phải bao hàm yếu tố xảy ra trường hợp dừng.
- Ví dụ hàm tính giai thừa:
(defun giai_thua(n)
(if (= n 0) 1 ; trường hợp dừng
(* n (giai_thua (- n 1))))) ; đệ quy
3. ỨNG DỤNG GIẢI BÀI TOÁN “THÁP HÀ NỘI”
Trong phần này tiểu luận sẽ tình bày về bài toán “Tháp Hà Nội”, phân tích
bài toán dưới góc độ đệ quy, áp dụng viết chương trình giải trên LISP.
3.1. Bài toán “Tháp Hà Nội”
Về mặt lịch sử, Tháp Hà Nội được E.Lucas phát hiện từ năm 1883, nhưng
chưa rõ vì sao Lucas lại gọi chồng đĩa trong bài toán là Tháp Hà Nội. Bài toán
thường được lấy làm ví dụ trong các giải thuật đệ quy khi trình bày trong các
cuốn sách về lập trình. Lời giải tối ưu cho bài toán có thể tìm thấy chính xác
trong trường hợp ba cọc, nhưng khi mở rộng số cọc thì hiện nay lời giải chính
xác vẫn chưa được khẳng định, vì thế trong phạm vi tiểu luận, chúng ta sẽ xem
xét bài toán với ba cọc đĩa.
Nhóm 11

13
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
Bài toán: Có n đĩa kích thước nhỏ dần xếp chồng lên nhau trên một cọc
(được gọi là cọc nguồn A), đĩa lớn ở dưới, đĩa nhỏ ở trên. Ngoài cọc nguồn còn
có 02 cọc trống khác được gọi là cọc đích (B) và cọc trung gian (C). Hãy chuyển
các đĩa này từ cọc nguồn sang cọc đích theo luật:
- Mỗi lần chỉ được chuyển 01 đĩa từ cọc này sang cọc khác và được
dùng cọc còn lại làm cọc trung chuyển.
- Không được xếp đĩa lớn nằm trên đĩa nhỏ.
Về phương pháp giải có thể thấy: để chuyển n đĩa từ cọc A sang cọc B cần
thực hiện:
- Chuyển n – 1 đĩa từ cọc A sang cọc C, còn lại đĩa thứ n trên cọc A.
- Chuyển đĩa thứ n sang cọc B.
- Chuyển n – 1 đĩa từ cọc C sang cọc B.
Ta có thể thấy ngay lời giải của bài toán là giải thuật đệ quy với lý do:
Công việc lặp lại n – 1 lần khi chuyển đĩa từ A sang C và C sang B, toàn bộ quá
trình là hữu hạn bước chuyển, đến một thời điểm nào đó giải thuật sẽ áp dụng
với n = 1.
Bỏ qua việc đi sâu phân tích vào bài toán, ở đây ta giới hạn ở việc nhìn ra
lời giải đệ quy của bài toán và áp dụng xây dựng chương trình trên Lisp để thực
hiện.
3.2. Lập trình trên Lisp
Chương trình được xây dựng trên Lisp có định nghĩa 04 hàm để thực hiện
chức năng đệ quy và trình bày lời giải. Ngoài ra chương trình khai báo các biến
toàn cục đại diện cho các tháp và đếm số lần dịch chuyển.
Áp dụng trên Lisp, tận dụng khả năng giải đệ quy và xử lý danh sách nên
ta có mô tả:
3.2.1. Biến số:
- *thapA* đại diện cho cột A, là cột nguồn biểu diễn danh sách các đĩa
trong đó các đĩa được đánh số từ nhỏ tới lớn tương ứng việc đĩa nhỏ

nằm trên đĩa lớn. *thapA* có dạng: (1 2 3 4 5 6…).
- *thapB* là tháp đích, khởi tạo bởi danh sách trống.
- *thapC* là tháp trung gian
Nhóm 11
14
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
- *count* là biến đếm số lần dịch chuyển
3.2.2. Hàm thực hiện:
- Hàm IN_CHI_TIET: có chức năng in lại tình trạng các tháp để có được
cái nhìn sau mỗi lần dịch chuyển.
(defun in_chi_tiet()
(format t "Thap A: ~A,"
(if (= (length *thapA*) 0) "()" (reverse *thapA*)))
(format t "Thap B: ~A,"
(if (= (length *thapB*) 0) "()" (reverse *thapB*)))
(format t "Thap C: ~A~%"
(if (= (length *thapC*) 0) "()" (reverse *thapC*)))
) ; in tình trạng các tháp sau mỗi lần thực hiện
- Hàm CHUYEN: thưc hiện việc chuyển 1 đĩa từ tháp này sang tháp kia,
sử dụng cơ chế POP và PUSH
(defun chuyen(thapNguon thapDich)
(if (not (eq thapNguon thapDich))
(let ((tg 0)) ; khai báo biến trung gian
; đếm số lần chuyển
(setq *count* (1+ *count*))
; pop phần tử trong danh sách tương ứng
(if (eq thapNguon 'A) (setq tg (pop *thapA*)))
(if (eq thapNguon 'B) (setq tg (pop *thapB*)))
(if (eq thapNguon 'C) (setq tg (pop *thapC*)))
; push phần tử vào danh sách tương ứng

(if (eq thapDich 'A) (push tg *thapA*))
(if (eq thapDich 'B) (push tg *thapB*))
(if (eq thapDich 'C) (push tg *thapC*))
; in thông tin chuyển đĩa
(format t "Chuyen dia ~A tu thap ~A sang thap ~A: "
tg thapNguon thapDich)
Nhóm 11
15
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
(in_chi_tiet)
)
)
)
- Hàm DE_QUY: là hàm đệ quy thực hiện công việc chuyển các đĩa
theo thuật toán đã phân tích trong 3.1:
(defun de_quy(solan thapNguon thapDich thapTrungGian)
(if (> solan 0)
(progn
(de_quy (1- solan) thapNguon thapTrungGian thapDich)
(chuyen thapNguon thapDich)
(de_quy (1- solan) thapTrungGian thapDich thapNguon)
)))
- Hàm THAP_HA_NOI: thực hiện khởi tạo và trình bày bài toán:
(defun thap_ha_noi()
(setq *thapA* '(1 2 3 4 5))
(setq *thapB* '())
(setq *thapC* '())
(setq *count* 0)
(let ((sodia (length *thapA*)))
(format t "So dia: ~A~%" sodia)

(format t "Ban dau: ")
(in_chi_tiet)
(de_quy sodia 'A 'B 'C)
(format t "Ket thuc: ")
(in_chi_tiet)
(format t "So lan chuyen: ~A" *count*)
)
)
- Chương trình bắt đầu thực hiện với lời gọi:
> (THAP_HA_NOI)
3.3. Kết quả chạy ứng dụng
Nhóm 11
16
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
Chương trình thực hiện tốt, kiểm tra đã thấy chạy đúng thuật toán. Trong
trường hợp tăng số đĩa thì số lần di chuyển tăng nhanh theo đúng công thức
(2
so_dia
- 1). Lời giải trong trường hợp 5 đĩa:
So dia: 5
Ban dau: Thap A: (5 4 3 2 1),Thap B: (),Thap C: ()
Dia 1 tu thap A sang thap B: Thap A: (5 4 3 2),Thap B: (1),Thap C: ()
Dia 2 tu thap A sang thap C: Thap A: (5 4 3),Thap B: (1),Thap C: (2)
Dia 1 tu thap B sang thap C: Thap A: (5 4 3),Thap B: (),Thap C: (2 1)
Dia 3 tu thap A sang thap B: Thap A: (5 4),Thap B: (3),Thap C: (2 1)
Dia 1 tu thap C sang thap A: Thap A: (5 4 1),Thap B: (3),Thap C: (2)
Dia 2 tu thap C sang thap B: Thap A: (5 4 1),Thap B: (3 2),Thap C: ()
Dia 1 tu thap A sang thap B: Thap A: (5 4),Thap B: (3 2 1),Thap C: ()
Dia 4 tu thap A sang thap C: Thap A: (5),Thap B: (3 2 1),Thap C: (4)
Dia 1 tu thap B sang thap C: Thap A: (5),Thap B: (3 2),Thap C: (4 1)

Dia 2 tu thap B sang thap A: Thap A: (5 2),Thap B: (3),Thap C: (4 1)
Dia 1 tu thap C sang thap A: Thap A: (5 2 1),Thap B: (3),Thap C: (4)
Dia 3 tu thap B sang thap C: Thap A: (5 2 1),Thap B: (),Thap C: (4 3)
Dia 1 tu thap A sang thap B: Thap A: (5 2),Thap B: (1),Thap C: (4 3)
Dia 2 tu thap A sang thap C: Thap A: (5),Thap B: (1),Thap C: (4 3 2)
Dia 1 tu thap B sang thap C: Thap A: (5),Thap B: (),Thap C: (4 3 2 1)
Dia 5 tu thap A sang thap B: Thap A: (),Thap B: (5),Thap C: (4 3 2 1)
Dia 1 tu thap C sang thap A: Thap A: (1),Thap B: (5),Thap C: (4 3 2)
Dia 2 tu thap C sang thap B: Thap A: (1),Thap B: (5 2),Thap C: (4 3)
Nhóm 11
17
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
Dia 1 tu thap A sang thap B: Thap A: (),Thap B: (5 2 1),Thap C: (4 3)
Dia 3 tu thap C sang thap A: Thap A: (3),Thap B: (5 2 1),Thap C: (4)
Dia 1 tu thap B sang thap C: Thap A: (3),Thap B: (5 2),Thap C: (4 1)
Dia 2 tu thap B sang thap A: Thap A: (3 2),Thap B: (5),Thap C: (4 1)
Dia 1 tu thap C sang thap A: Thap A: (3 2 1),Thap B: (5),Thap C: (4)
Dia 4 tu thap C sang thap B: Thap A: (3 2 1),Thap B: (5 4),Thap C: ()
Dia 1 tu thap A sang thap B: Thap A: (3 2),Thap B: (5 4 1),Thap C: ()
Dia 2 tu thap A sang thap C: Thap A: (3),Thap B: (5 4 1),Thap C: (2)
Dia 1 tu thap B sang thap C: Thap A: (3),Thap B: (5 4),Thap C: (2 1)
Dia 3 tu thap A sang thap B: Thap A: (),Thap B: (5 4 3),Thap C: (2 1)
Dia 1 tu thap C sang thap A: Thap A: (1),Thap B: (5 4 3),Thap C: (2)
Dia 2 tu thap C sang thap B: Thap A: (1),Thap B: (5 4 3 2),Thap C: ()
Dia 1 tu thap A sang thap B: Thap A: (),Thap B: (5 4 3 2 1),Thap C: ()
Ket thuc: Thap A: (),Thap B: (5 4 3 2 1),Thap C: ()
So lan chuyen: 31
Nhóm 11
18
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”

KẾT LUẬN
Tiểu luận đã trình bày các vấn đề:
- Khái niệm, nguyên lý chung và các đặc trưng của ngôn ngữ lập trình
hàm.
- Các đặc trưng cơ bản, các định nghĩa, quy tắc và hàm cơ bản của ngôn
ngữ lập trình Lisp.
- Giới thiệu bài toán “Tháp Hà Nội”, xây dựng chương trình trên ngôn
ngữ Lisp, thực hiện và cho kết quả.
Qua các trình bày cho thấy: việc tìm hiểu và nghiên cứu ngôn ngữ lập
trình hàm là một phạm vi rất rộng, sự gắn bó chặt chẽ giữa lập trình hàm và hàm
toán học đòi hỏi việc tiếp cận phải có một kiến thức tốt về toán.
Về mặt tư duy, lập trình hàm khác biệt khá nhiều so với lập trình mệnh
lệnh nên điều này ít nhiều cũng sẽ khó khăn với các lập trình viên đã quen thuộc
với ngôn ngữ lập trình mệnh lệnh khi bắt đầu tiếp cận.
Việc tìm hiểu lập trình hàm thông qua ngôn ngữ Lisp cũng cho thấy Lisp
là một ngôn ngữ rất mạnh về xử lý danh sách, xử lý đệ quy và thuần túy theo
hướng lập trình hàm. Chương trình xây dựng trên Lisp tương đối ngắn gọn tuy
nhiên cũng là khó khăn khi áp dụng xây dựng ứng dụng thương mại do thiếu các
cộng đồng phát triển mạnh mẽ như lập trình mệnh lệnh.
Tiểu luận chưa đi sâu được vào các vấn đề phức tạp hơn của lập trình
hàm, tuy nhiên đã tạo điều kiện tiếp cận sơ lược về hướng tư duy này, mở đường
cho các tiếp cận sâu hơn trong thực tế khi áp dụng các bài toán phù hợp.
Nhóm 11
19
Lập trình hàm – tìm hiểu và áp dụng ngôn ngữ Lisp giải bài toán “Tháp Hà Nội”
TÀI LIỆU THAM KHẢO
1. Bài giảng “Nguyên lý các ngôn ngữ lập trình” TS. Cao Tuấn Dũng
2. Lập trình hàm – TS. Phan Huy Khánh – NXB Khoa học kỹ thuật – 2007
3. Hppt://www.lispworks.com
4. Nguồn tham khảo Internet:

Nhóm 11
20

×