You are on page 1of 26

THUẬT GIẢI VÀ CHƯƠNG TRÌNH

1. Các khái niệm cơ bản

1.1. Khoa học máy tính

Khoa học máy tính là ngành khoa học nghiên cứu các giải pháp giải
quyết các bài toán hay vấn đề thực tế đặt ra. Trong tài liệu này, ta
sẽ gọi là bài toán thực tế, hay đơn giản là bài toán. Từ bài toán được
cho, nhiệm vụ của ta là phát triển một thuật giải, nhiều tài liệu còn
gọi là thuật toán, để giải quyết mọi khả năng có thể nảy sinh của bài
toán cần giải. Mặc dù, khoa học máy tính là nghiên cứu giải quyết bài
toán thực tế bằng thuật giải. Tuy nhiên, cần thận trọng là vì trong
thực tế có nhiều bài toán có thể không có giải pháp, nghĩa là không có
thuật giải hoặc tuy có thuật giải nhưng thuật giải đó lại không thể
cho lời giải trong thời gian chấp nhận được. Do đó, một cách hiểu đầy
đủ về khái niệm khoa học máy tính như sau:

Định nghĩa 1.1. Khoa học máy tính là ngành khoa học nghiên cứu tính
giải được (solvable) của một bài toán trong thực tế, và nếu giải được
thì tìm giải pháp (solution) cho bài toán đó.

Một bài toán có thể giải được hay tính toán nếu tồn tại một thuật giải
để tính lời giải cho nó. Do đó, cũng có thể hiểu khoa học máy tính
cách khác như sau:

Định nghĩa 1.2. Khoa học máy tính là ngành khoa học nghiên cứu là
ngành khoa học nghiên cứu thế nào bài toán tính toán (computable) được
và không thể tính toán được (uncomputable), nghĩa là, nghiên cứu về sự
tồn tại và không tồn tại của các thuật toán.

Lưu ý là giải pháp độc lập với máy tính.

1.2. Trừu tượng hoá

Trừu tượng hoá (abstraction) là kỹ thuật của khoa học máy tính được
dùng để đơn giản hoá quá trình giải quyết bài toán thực tế được cho.
Hiểu đơn giản, trừu tượng hoá là cách tách biệt nội dung và hình thức
của bài toán theo các góc nhìn thiết kế hay sử dụng. Ở góc nhìn sử
dụng, người dùng chỉ quan tâm đến các tính năng (function) bài toán
mang lại và quy trình (workflow) sử dụng các tính năng đó; trong khi
đó, ở góc nhìn thiết kế, người thiết kế không chỉ vận hành được các
chức năng mà phải hiểu tại sao các chức năng đó vận hành. Nói cách
khác, người dùng quan tâm đến giao diện còn người thiết kế lại chú tâm
đến logic của bài toán.

Ví dụ 2.1.
Để tính √169, ta nhập các lệnh Python sau:

> import math

> math.sqrt(169)

13.0

>

Ta đã sử dụng hàm (function) tính căn bậc hai (sqrt) của bài toán tính
số math mà không cần biết làm sao để tính được √169, một lập trình viên
nào đó đã làm việc đó, ta chỉ cần biết cách sử dụng nó.

Ở góc nhìn sử dụng, ta xem math như một hộp đen (black box) cung cấp
các phương thức (method) tính số, như ta đã dùng phương thức
math.sqrt(196) của math để tính √169. Theo đó, chỉ cần mô tả giao diện:
tên phương thức, những gì cần cho phương thức (input - đầu vào) và
những gì muốn nhận từ phương thức (output - đầu ra).

Ở góc nhìn thiết kế, lập trình viên phải thiết kế được giải pháp để
chuyển từ cái cần (input) sang cái muốn (output) cho hộp đen, gọi là
thuật giải, và lập trình giải pháp đó trên máy tính.

1.3. Lập trình

Lập trình là mã hoá (coding) một thuật giải bằng một ngôn ngữ lập
trình để máy tính có thể thực thi thuật giải đó. Có nhiều ngôn ngữ lập
trình và nhiều loại máy tính khác nhau, nhưng quan trọng nhất là phải
có giải pháp hay thuật giải (algorithm). Không có thuật giải thì không
thể có chương trình.

Khoa học máy tính không phải là lập trình (programming). Nhưng, lập
trình là một phần quan trọng phải được thực hiện để đưa giải pháp vào
máy tính thi hành. Nói cách khác, lập trình là tạo ra một thể hiện
(instance) của giải pháp. Lập trình là phần cơ bản của KHMT.

Thuật giải mô tả giải pháp giải một vấn đề bằng các bước biến đổi cần
thiết để tạo ra kết quả. Ngôn ngữ lập trình phải cung cấp các cách mã
hoá (chỉ thị/lệnh) để biểu diễn quá trình biến đổi biến đổi cũng như
biểu diễn dữ liệu, gọi chung là cấu trúc điều khiển và kiểu dữ liệu.

Cấu trúc điều khiển cho phép biểu diễn các bước thuật giải được rõ
ràng. Có 3 cấu trúc cơ bản: tuần tự (sequence), chọn (selection), và
lặp (iteration).
Mọi ngôn ngữ cung cấp 3 cấu trúc cơ bản này, đều có thể biểu diễn
thuật toán1.

Mọi dữ liệu trong máy tính được biểu diễn dưới dạng chuỗi nhị phân.
Kiểu dữ liệu là cách quy ước ý nghĩa cho các chuỗi nhị phân, qua đó ta
có thể tư duy về dữ liệu bằng các thuật ngữ phù hợp với vấn đề đang
được giải. Các kiểu dữ liệu cho sẵn, kiểu dữ liệu nguyên thủy, là kiểu
mà ngôn ngữ cung cấp kiến trúc bao gồm dữ liệu và phép toán, một dạng
trừu tượng hoá, để cài đặt thuật giải.

Khó khăn là trong thực tế, các vấn đề và giải pháp thường rất phức
tạp. Cấu trúc điều khiển và kiểu dữ liệu nguyên thủy, do ngôn ngữ cung
cấp, cho dù đã đủ để biểu diễn các giải pháp phức tạp, nhưng thường
khó khăn trong quá trình suy nghĩ giải quyết vấn đề. Cách để kiểm soát
sự phức tạp này và giúp dễ dàng tạo ra giải pháp gọi là kiểu dữ liệu
trừu tượng bao gồm cấu trúc dữ liệu và phép toán thao tác cấu trúc dữ
liệu đó.

1.4. Trừu tượng hoá dữ liệu và kiểu dữ liệu trừu tượng

Để quản lý độ phức tạp của bài toán và đơn giản hoá quá trình giải bài
toán, tiếp cận trừu tượng hoá là tập trung vào tổng thể mà không bị sa
đà vào tiểu tiết bài toán. Bằng cách mô hình hoá miền xác định
(domain) của bài toán, ta có thể giải quyết bài toán hiệu quả hơn. Mô
hình dữ liệu cho phép đơn giản hoá thuật giải để quản lý các thao tác
nhất quán hơn.

Trước đây, kỹ thuật lập trình đơn thể (module) tập trung vào trừu
tượng hóa chức năng, nghĩa là che giấu các chi tiết của một chức năng
(function) để chỉ còn thấy nó ở mức tổng quát cao. Hiện nay, trừu
tượng hoá tập trung vào dữ liệu, trừu tượng hóa dữ liệu.

Một kiểu dữ liệu trừu tượng - ADT (Abstract Data Type), là tập mô tả
cả định dạng (format) dữ liệu lẫn các phép toán (operation) được phép
thao tác dữ liệu đó và không quan tâm đến cách thực hiện. Nghĩa là ta
chỉ quan tâm đến dữ liệu đang biểu diễn thứ gì chứ không quan tâm đến
cụ thể nó sẽ được xây dựng như thế nào. Ý tưởng chính là che giấu
thông tin nghĩa là đóng gói mọi chi tiết cài đặt.

Kiểu dữ liệu trừu tượng được xem như hộp đen và người dùng tương tác
với nó mà không phải quan tâm đến chi tiết thực hiện. Việc cài đặt
được che giấu đi. Ngược lại, cài đặt một kiểu dữ liệu trừu tượng cần
quan tâm đến khía cạnh vật lý của dữ liệu theo nghĩa sử dụng các cấu
trúc điều khiển và kiểu dữ liệu nguyên thủy. Như đã trình bày trong
mục 2 - trừu tượng hoá, việc tách biệt hai góc nhìn sử dụng và thiết

1
Trong tài liệu này, ta sẽ sử dụng ngôn ngữ Python
kế cho phép ta định nghĩa mô hình dữ liệu cho bài toán của ta mà không
đưa ra bất kỳ chi tiết nào về cách mô hình sẽ được xây dựng. Điều này
cho phép xem xét dữ liệu độc lập với cài đặt. Vì thường có nhiều cách
cài đặt một kiểu dữ liệu trừu tượng, nên tính độc lập này cho phép lập
trình viên có thể thay đổi chi tiết cài đặt mà không làm thay đổi cách
người dùng dữ liệu tương tác với nó. Người dùng chỉ cần tập trung vào
quá trình giải quyết bài toán.

1.5. Phân tích thuật giải

Theo kinh nghiệm, người ta học được bằng cách quan sát người khác giải
quyết vấn đề và bằng cách tự mình giải quyết vấn đề. Tiếp xúc với các
kỹ thuật giải quyết vấn đề khác nhau và xem các thuật giải khác nhau
được thiết kế như thế nào sẽ giúp ta giải quyết vấn đề mà ta sẽ được
giao sau này. Xem xét một số thuật giải khác nhau, ta có thể phát
triển tư duy nhận dạng mẫu để khi gặp vấn đề tương tự, ta có thể giải
quyết tốt hơn.

Các thuật giải thường khác nhau. Chẳng hạn với phương thức sqrt()ta đã
sử dụng ở trên, có thể có nhiều cách khác nhau để thực hiện các chi
tiết tính căn bậc hai. Nhiều khi, 2 thuật giải cùng cài đặt một phương
thức, thuật giải này có thể sử dụng ít tài nguyên hơn thuật giải kia.
Một thuật giải có thể mất thời gian gấp 10 lần để trả về cùng kết quả
như thuật giải khác đã làm. Mặc dù cả hai thuật giải đều hoạt động,
nhưng cái này có thể tốt hơn cái kia. Ta xem thuật giải này hiệu quả
hơn thuật giải kia nếu hoặc là nó thực hiện nhanh hơn hoặc nó sử dụng
ít bộ nhớ hơn, hoặc cả hai. Khi nghiên cứu thuật giải, ta học các kỹ
thuật phân tích để so sánh và đối chiếu các thuật giải chỉ dựa trên
các đặc trưng của chúng chứ không phải đặc điểm của chương trình hoặc
máy tính được sử dụng để thực hiện chúng.

Trong trường hợp xấu nhất, ta gặp phải một bài toán khó (hard
problem), nghĩa là không có thuật giải nào có thể giải quyết nó trong
một khoảng thời gian chấp nhận được. Điều quan trọng của phân tích
thuật giải là có thể phân biệt giữa những bài toán có thuật giải,
những bài toán không có và những bài toán có giải pháp nhưng cần quá
nhiều thời gian hoặc các nguồn lực khác để hoạt động.

Thường sẽ có đánh đổi cần phải xác định và phải quyết định. Như vậy,
ngoài khả năng giải quyết bài toán, ta cũng cần biết và hiểu các kỹ
thuật phân tích thuật giải. Cuối cùng, thường có nhiều cách để giải
quyết một bài toán. Tìm kiếm một giải pháp và sau đó quyết định xem nó
có phải là một giải pháp tốt hay không là nhiệm vụ mà ta sẽ làm đi làm
lại.

2. Python cơ bản
Tài liệu này sử dụng ngôn ngữ lập trình Python để mã hoá thuật giải.
Python là ngôn ngữ lập trình hướng đối tượng (OOP – Object Orientic
Programming) hiện đại, dễ học. Python cung cấp sẵn một tập các kiểu dữ
liệu đủ mạnh và cấu trúc điều khiển dễ sử dụng.

Python là ngôn ngữ thông dịch (interpreter), nghĩa là máy tính nhận
từng lệnh của người dùng, thực thi nó, và chờ nhận lệnh khác:

trình thông dịch hiển thị lời nhắc  người dùng nhập vào lệnh  thực
thi và trả kết quả  trình thông dịch hiển thị lời nhắc tiếp ...

Ví dụ: để in "Python programming", ta nhập lệnh print với tham số


“Python programming” tại dấu nhắc ">". Python thi hành lệnh và in ra
màn hình như ta muốn, rồi lại nhắc ">" nhập lệnh khác.

> print("Python programming")


Python programming
>
Khi ta muốn thi hành một thuật giải gồm nhiều lệnh, ta cần viết chương
trình sử dụng các cấu trúc lệnh như ta đã được giới thiệu trong phần
trước.

2.1. Dữ liệu

Python hỗ trợ lập trình hướng đối tượng, nghĩa là Python coi dữ liệu
là tâm điểm của quá trình giải quyết vấn đề. Trong Python, cũng như
trong bất kỳ ngôn ngữ lập trình hướng đối tượng nào khác, ta định
nghĩa một lớp (class) để mô tả đối tượng dữ liệu như thế nào (trạng
thái) và dữ liệu có thể làm những gì (hành vi). Khái niệm lớp tương tự
với khái niệm kiểu dữ liệu trừu tượng ta học ở phần trước vì người
dùng chỉ nhìn thấy trạng thái và hành vi của một mục dữ liệu. Các mục
dữ liệu được gọi là các đối tượng trong mô hình hướng đối tượng. Một
đối tượng là một thể hiện của một lớp.

2.1.1. Các kiểu dữ liệu tích hợp cơ bản

Python có hai lớp dữ liệu số được tích hợp sẵn với các kiểu dữ liệu số
nguyên (integer) và số thực dấu chấm động (float). Đó là các lớp int
và float. Các phép toán số học thông thường, +, -, *, /, và ** (lũy
thừa), có thể được sử dụng với dấu ngoặc đơn để quy định thứ tự thực
hiện các phép toán như ta đã biết. Bên cạnh đó, Python cũng cung cấp
thêm các phép toán hữu ích khác là phép chia lấy phần dư (modulo),% và
phép chia nguyên, //. Lưu ý rằng khi chia hai số nguyên bằng phép /,
kết quả là số thực dấu chấm động. Toán tử chia nguyên // trả về phần
nguyên của thương bằng cách cắt bớt phần phân số thập phân. Ví dụ:

print(2+3*4)

print((2+3)*4)
print(2**10)

print(6/3)

print(7/3)

print(7//3)

print(7%3)

print(3/6)

print(3//6)

print(3%6)

print(2**100)

Kiểu dữ liệu luận lý (boolean), được cài đặt trong lớp bool, biểu diễn
các chân trị. Các giá trị trạng thái có thể có cho đối tượng bool là
True và False với các toán tử bool: and, or, và not thông thường.

> True

True

> False

False

> False or True

True

> False and True

False

> not (False or True)

False

>

Đối tượng dữ liệu Bool cũng được sử dụng làm kết quả các toán tử so
sánh, các toán tử quan hệ và toán tử logic có thể được kết hợp với
nhau tạo thành các mệnh đề logic phức tạp.

print(5==3)

print(3 > 5)

print((2 >= 1) and (5 <= 7))

Định danh (Identity) được sử dụng trong ngôn ngữ lập trình như tên
(biến, hàm, lớp, phương thức,...). Định danh bắt đầu bằng một chữ cái
hoặc dấu gạch dưới (_), phân biệt chữ hoa chữ thường và có thể có độ
dài bất kỳ.

Nên sử dụng những tên gợi nhớ để chương trình dễ đọc và dễ hiểu hơn.

Một biến được tạo ra khi tên của nó được sử dụng lần đầu tiên ở vế
trái của lệnh gán. Lệnh gán kết buộc (binding) một tên với một giá trị.
Biến sẽ giữ một tham chiếu đến một phần dữ liệu chứ không phải chính
dữ liệu đó. Ví dụ:

> sum = 10+5

> sum

15

> sum = sum + 1

> sum

16

> sum = True

> sum

True

>

2.1.2. Kiểu dữ liệu dạng tập hợp nhiều thành phần

Ngoài các lớp số và lớp bool, Python cung cấp sẵn một số lớp dạng tập
hợp (collection). Đó là các cấu trúc:

 Dạng tập hợp có trật tự (thứ tự theo chỉ số) danh sách (list), chuỗi
(string) và bộ (type). Các cấu trúc này rất giống nhau về cấu trúc
nhưng có một số khác biệt cụ thể mà ta phải phân biệt được để sử dụng
cho đúng cách.

 Dạng tập hợp không có trật tự: Tập hợp (set) và từ điển (dictionary).

2.1.2.1. Danh sách – list

Là một tập hợp có trật tự gồm không hoặc nhiều tham chiếu đến các đối
tượng dữ liệu. Danh sách được viết dưới dạng liệt kê các giá trị và
được phân cách bằng dấu phẩy – “,”, đặt trong dấu ngoặc vuông, [ ...
]. Danh sách trống đơn giản là []. Danh sách là đối tượng dữ liệu
không đồng nhất, có nghĩa là các thành phần đối tượng dữ liệu không
nhất thiết phải thuộc cùng một lớp và danh sách có thể được gán cho
một biến. Ví dụ:
> [2, 3.14, True, "string", ['l','i','s','t']]

[2, 3.14, True, 'string', ['l', 'i', 's', 't']]

> myList = [2, 3.14, True, "string", ['l','i','s','t']]

> myList

[2, 3.14, True, 'string', ['l', 'i', 's', 't']]

>

Lưu ý rằng khi Python lượng giá một danh sách, danh sách đó sẽ được
trả về như kết quả. Tuy nhiên, để có thể dùng lại, tham chiếu của nó
cần được gán cho một biến.

Các phương thức thao tác danh sách, Chú ý rằng trong Python, chỉ số
bắt đầu đếm từ 0:

Input: L Phương thức Output


[1, 2, 4] L[1] #lấy phẩn tử theo chỉ số 2
[‘a’, 1, False] L + [True] #ghép danh sách [‘a’, 1, False, True]
[1, 2] L * 3#nhân bản danh sách [1, 2, 1, 2, 1, 2]
[3.14, 1, False] 1 in L#kiểm tra phần tử True
[1, 7, 3, 4, 6] Len(L) #số phần tử của danh sách 5
[1, 2, ‘pi’, 4, 5] S[1:3] #lấy ra danh sách con [2, ‘pi’]

Để khởi tạo một danh sách nhiều phần tử cùng giá trị, có thể sử dụng
“toán tử lặp”. Ví dụ,

> myList = [0]*10

> myList

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

>

Trong trường hợp cần một danh sách các phần tử có giá trị khác nhau,
thường là một cấp số cộng, ta có thể sử dụng hàm range, với cú pháp

range(from, to, step).

Ví dụ:

list1 = list(range(5))

print(list1)

list2 = list(range(2,11,2))

print(list2)
list3 = list(range(10,0,-2))

print(list3)

Lưu ý quan trọng liên quan đến toán tử lặp là kết quả là sự lặp lại
các tham chiếu đến các đối tượng dữ liệu trong tập. Ví dụ:

> myList = [1,2,3]

> A = myList * 2

> A

[1, 2, 3, 1, 2, 3]

> myList[2]=7

> A

[1, 2, 3, 1, 2, 3]

>

Biến A chứa một tập hợp ba tham chiếu đến danh sách myList. Lưu ý rằng
thay đổi một phần tử của myList hiển thị trong cả ba lần xuất hiện
trong A.

Lớp list cung cấp thêm một số phương thức để có thể xây dựng cấu trúc
dữ liệu trừu tượng khác. Trong bảng sau, giả sử ta có danh sách với
tên biến là L.

Input: L Phương thức Output: L


[1,7,3] L.append(5)#thêm phần tử [1, 7, 3, 5]
[3, 5, 2] L.clear()#xoá các phần tử []
[2, 7, 7] L.count(7) #đếm phần tử cần 2
[4, 2, 0] del L[1] #xoá phần tử muốn [4, 0]
[5, 7, 9] i = L.index(9) #chỉ số phần tử cần 2
[3, 7, 9] L.insert(1,5)#thêm phần tử vào vị trí [3, 5, 7, 9]
[4, 7, 3] e = L.pop()#lấy/xoá phần tử [4, 7], e = 3
[3, 2, 7] e = L.pop(1) #lấy/xoá tại vị trí cần [3, 7], e = 2
[5, 9, 3] max(L) #trả về phần tử lớn nhất 9
[4, 1, 7] min(L) #trả về phần tử nhỏ nhất 1
[4, 7, 5] L.remove(7) #huỷ phần tử [4, 5]
[1, 5, 9] L.reverse()#đảo danh sách [9, 5, 1]
[2, 1, 5] L.sort()#sắp xếp các phần tử [1, 2, 5]

2.1.2.2. Chuỗi ký tự - string

Là tập hợp có trật tự của không hoặc nhiều chữ cái, chữ số và các ký
hiệu khác. Ta gọi chữ cái, chữ số và các ký hiệu khác là character.

> language = "Python"


> language[2:4]

'th'

> len(language)

>

Giả sử chuỗi có tên biến là S, string cung cấp một số phương thức như
sau.

Input: S Phương thức Output


‘PythonLanguage’ S.center(18)# in căn giữa --PythonLanguage--
‘banana, apple’ S.count(‘an’)#đếm số chuỗi con 2
‘father – mother’ S.find(‘the’)#tìm chuỗi con 2
‘PythonLanguage’ S.ljust(18)# in căn trái PythonLanguage----
‘Python Language’ S.lower()#chuyển tất cả sang chữ thường python language
‘PythonLanguage’ S.rjust(18)# in căn phải ----PythonLanguage
‘sons and sons’ S.split(‘and’) #cắt chuỗi con [‘sons’, ‘sons’]
‘Python Language’ S.upper()#chuyển tất cả sang chữ in PYTHON LANGUAGE
Khác biệt chính giữa list và string là list có thể được sửa đổi trong
khi string thì không.

List có thể thay đổi; string thì bất biến.

Ví dụ: ta có thể thay đổi một thành phần của danh sách bằng chỉ số và
phép gán, nhưng với chuỗi thì không.

> string = "Python"


> string
'Python'
> string[0] = 'p'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>
2.1.2.3. Bộ - tuple

Giống với list ở chỗ chúng là dãy các phần tử dữ liệu không đồng nhất.
Khác biệt ở chỗ tuple bất biến, giống như string. Không thể thay đổi
một tuple. Các phần tử dữ liệu được viết dạng các giá trị và phân cách
bằng dấu phẩy đặt trong dấu ngoặc đơn, ( … ). Ví dụ,

> myType = (2, 'Python', 3.14)

> myType

(2, 'Python', 3.14)

> myType[1]

'Python'
> myType[1] = 3

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: 'tuple' object does not support item assignment

>

2.1.2.4. Tập hợp - set

Là một tập hợp không có trật tự gồm không hoặc nhiều đối tượng dữ liệu
bất biến. set không cho phép có phần tử trùng lặp và được viết dưới
dạng liệt kê giá trị, phân tách bằng dấu phẩy được đặt trong dấu ngoặc
nhọn, { … }. Tập hợp rỗng được biểu diễn bằng set(). Tập hợp là tập
các phần tử không đồng nhất và tập hợp có thể được gán cho một biến.

> mySet = {1,3.14,'Python'}


> mySet
{'Python', 1, 3.14}
> emptySet = set()
> emptySet
set()
>

Input: S Phương thức Output: S


{1, ‘apple’} {‘a’, 1} | S #hội {'a', 1, ‘apple’}
{1, ‘apple’} {‘a’, 1} & S #giao {1}
{1, ‘apple’} {‘a’, 1} – S #hiệu {‘a’}
{1, ‘apple’} {‘a’, 1} <= S #tập con False
{1,‘apple’, 3.14} 3.13 in S #thuộc True
{0} S.issubset({2.7, 0})#tập con True
{1, 2, 3} S.difference({2, 4})#hiệu {1,3}
{1, ‘pi’, 5} S.intersection({‘pi’, 1})#giao {1, ‘pi’}
{1, 2} S.union({2, 4})#hội {1, 2, 4}
{1, 2, True} Len(S) #số phần tủ của tập hợp 3
{3, 5} S.add(False) #thêm phần tử {False, 3, 5}
{‘a’, 3.14, 0} S.clear()#xoá hết phần tử set()
{1, 2, False} x = S.pop()#lấy và loại phần tử đầu {2, False}, x = 1
{1, 2, True} S.remove(2)#loại phần tử {1, True}
2.1.2.5. Từ điển – dictionary

Là tập hợp các cặp khóa-giá trị được viết dưới dạng khóa : giá trị. Từ
điển được liệt kê các cặp khóa : giá trị, phân cách bằng dấu phẩy : và
được đặt trong dấu ngoặc nhọn. Ví dụ,

EV_dic = {"one":1,"two":2,"three":3}
print(EV_dic)
database = {"item1":"orange","item2":"apple"}
print(database)
Có thể thao tác từ điển bằng cách truy cập một giá trị thông qua khóa
của nó hoặc bằng cách thêm một cặp khóa-giá trị khác. Cú pháp của truy
cập giống như truy cập theo trình tự chỉ khác là thay vì sử dụng chỉ
mục, ta sử dụng giá trị khóa. Để thêm một giá trị mới cũng tương tự.

EV_dic = {"one":1,"two":2,"three":3}

print(EV_dic)

database = {"item1":"orange","item2":"apple"}

print(database)

print(EV_dic['two'])

print(database['item1'])

for i in EV_dic:

print(EV_dic[i], i)

Từ điển có cả phương thức và toán tử. Các phương thức key, value và
item đều trả về các đối tượng chứa các giá trị quan tâm. Ta có thể sử
dụng hàm list để chuyển từ điển thành danh sách. Phương thức get có
hai biến thể. Nếu khóa không có trong từ điển, hàm get sẽ trả về none.
Tuy nhiên, tham số tùy chọn thứ hai có thể chỉ định giá trị trả về
thay thế.

Input: D Phương thức Output


{1:’app’,21:’b’} Dict[21] #lấy giátrị tại khoá ‘b’
{1:’a’,2:’b’} 2 in D #hỏi khoá trong từ điển True
{1:’apple’,25:’b’} del D[25] #xoá phần tử khoá {1:’apple’}
{1:’apple’,25:’b’} D.keys()#hỏi các khoá dict_keys([1, 21])
{1:’apple’,25:’b’} D.values()#hỏi các giá trị dict_keys([“apple”, “b”])
{1:’apple’,25:’b’} D.items()#hỏi từ điển dict_keys({1:’apple’,25:’b’})
{1:’apple’,25:’b’} D.get(1) #lấy giá trị có khoá ‘apple’
{1:’apple’,25:’b’} D.get(2,’exception’) ’exception’

2.2. Nhập và xuất

2.2.1. Nhập

Ta thường có nhu cầu tương tác với người dùng, để lấy dữ liệu hoặc
cung cấp một số giá trị. Hầu hết sử dụng hộp thoại để yêu cầu người
dùng cung cấp giá trị đầu vào. Python cung cấp hàm input cho phép yêu
cầu người dùng nhập một số dữ liệu và trả về tham chiếu đến dữ liệu
dưới dạng một chuỗi.

Hàm input nhận một tham số duy nhất là một string (chuỗi nhắc/gợi ý).

num = input("Number:")
name = input("Name:")

print(num, " - ", name)

Điều quan trọng cần lưu ý là giá trị trả về từ hàm input sẽ là một
chuỗi đại diện cho các ký tự chính xác đã được nhập. Nếu muốn chuỗi
này được hiểu là một kiểu khác, phải chuyển đổi kiểu tường minh. Trong
các câu lệnh dưới, chuỗi do người dùng nhập được chuyển thành float
(số thực) để có thể sử dụng như kiểu số.

l = input("lenght is:")
w = input("width is:")
area = float(l)*float(w)
print("Area is ", area)

2.2.2. Xuất

2.2.2.1. Hàm Xuất

Hàm print là cách rất đơn giản để chương trình xuất ra các giá trị.
print không có hoặc có nhiều tham số và hiển thị chúng ra, phân tách
mặc định là một khoảng trắng. Có thể thay đổi ký tự phân cách bằng
tham số sep. Ngoài ra, theo mặc định, print kết thúc bằng ký tự dòng
xuống dòng. Mặc định này có thể được thay đổi bằng cách đặt tham số
cho end.

print("Hello")
print("Hello", "Python")
print("Hello", "Python", sep = "-")
print("Hello", "Python", end = "*")
print()
2.2.2.1. Định dạng chuỗi

Python cũng cho phép chuỗi định dạng. Ví dụ, câu lệnh:

print(aName, "is", age, "years old.")


chứa các hằng từ is và years old, nhưng tên và tuổi tùy thuộc vào các
giá trị biến hiện tại. Ta cũng có thể viết dưới dạng:

print("%s is %d years old." % (aName, age))


Ví dụ này minh họa một biểu thức dạng chuỗi. “%” là một toán tử chuỗi
được gọi là toán tử định dạng. Bên trái % là chuỗi biểu thức định
dạng, đặt trong cặp ngoặc kép và có chứa %định_dạng, bên phải là tập
các giá trị sẽ được thay thế trong chuỗi tại các % tương ứng. Lưu ý
rằng số lượng giá trị ở bên phải phải tương ứng với số ký tự % trong
chuỗi định dạng. Các giá trị được lấy theo thứ tự, từ trái sang phải
trong tập và được chèn vào chuỗi định dạng.

Chuỗi định dạng có thể chứa một hoặc nhiều tham số chuyển đổi kiểu
(xuất). Một ký tự chuyển đổi kiểu sau toán tử định dạng ch biết loại
giá trị nào sẽ được chèn vào vị trí đó trong chuỗi. Trong ví dụ
trên,%s chỉ định một chuỗi (string), trong khi %d chỉ định một số
nguyên (digit). Các tham số kiểu khác gồm i, u, f, e, g, c hoặc %.

Ngoài ký tự định dạng, ta cũng có thể sửa định dạng để căn trái hoặc
căn phải giá trị với độ rộng vùng được chỉ định. Các dạng cũng có thể
được sử dụng để chỉ định độ rộng cùng với một số chữ số sau dấu thập
phân.

> price = 24
> item = "banana"
> print("The %s costs %d cents"%(item,price))
The banana costs 24 cents
> print("The %+10s costs %5.2f cents"%(item,price))
The banana costs 24.00 cents
> print("The %+10s costs %10.2f cents"%(item,price))
The banana costs 24.00 cents
> itemdict = {"item":"banana","cost":24}
> print("The %(item)s costs %(cost)7.1f cents"%itemdict)
The banana costs 24.0 cents
>

3. Cấu trúc điều khiển

Hai cấu trúc điều khiển quan trọng hầu hết ngôn ngữ lập trình cung cấp
sẵn là lặp và chọn. Cả hai đều được Python hỗ trợ ở nhiều dạng khác
nhau.

3.1. Cấu trúc lặp

Để lặp, dùng lệnh while chuẩn hay lệnh for. Lệnh while lặp lại một
phần nội dung đoạn mã lệnh khi điều kiện là đúng. Ví dụ,

>counter = 1
>while counter <= 5:
...print("Hi Python")
...counter = counter + 1

Hi Python
Hi Python
Hi Python
Hi Python
Hi Python

Trong nhiều trường hợp lệnh for có thể được sử dụng cùng với cấu trúc
tập hợp. Câu lệnh for có thể được sử dụng để duyệt qua các thành viên
của tập hợp. Ví dụ,

>for item in [1,3,6,2,5]:


... print(item)
...
1
3
6
2
5

Cách sử dụng phổ biến của câu lệnh for là khi số lần lặp xác định.
Chẳng hạn:

>for item in range(5):


...print(item**2)
...
0
1
4
9
16
>
Phiên bản khác của cấu trúc lặp for là để xử lý từng ký tự của một
chuỗi.

petlist = ['cat','dog','rabbit']

petlistlist = [ ]

for pet in petlist:

letterlist = []

for letter in pet:

letterlist = letterlist + [letter]

petlistlist.append(letterlist)

print(petlistlist)

3.2. Cấu trúc chọn

Cấu trúc chọn cho phép lập trình viên kiểm tra biểu thức điều kiện,
dựa trên kết quả, thực hiện các hành động khác nhau. Hầu hết ngôn ngữ
lập trình đều cung cấp hai phiên bản của cấu trúc: if-else và if. Một
ví dụ đơn giản về lựa chọn nhị phân sử dụng cấu trúc if-else.

if n<0:
print("value is negative")
else:
print(math.sqrt(n))
Cấu trúc chọn, cũng như với bất kỳ cấu trúc điều khiển nào, có thể
lồng vào nhau. Ví dụ

if score >= 90:


print('A')
else:
if score >=80:
print('B')
else:
if score >= 70:
print('C')
else:
if score >= 60:
print('D')
else:
print('F')
Hoặc thay thế cho kiểu chọn lồng nhau là sử dụng từ khóa elif.

if score >= 90:


print('A')
elif score >=80:
print('B')
elif score >= 70:
print('C')
elif score >= 60:
print('D')
else:
print('F')

3.3. Định nghĩa hàm

Ví dụ sqrt của thư viện maths ta đã sử dụng trước là hàm tính căn bậc
hai. Nói chung, có thể giấu các chi tiết của bất kỳ phép tính nào bằng
cách định nghĩa một hàm. Định nghĩa hàm yêu cầu xác định tên hàm, các
tham số và nội dung chi tiết. Hàm cũng có thể trả về giá trị. Ví dụ:

def square(n):
return n**2
print(square(12))
Các lệnh sau định nghĩa và cài đặt hàm tính căn bậc 2 bằng phương pháp
Newton:

def squareroot(n):
root = n/2 #initial guess will be 1/2 of n
for k in range(20):
root = (1/2)*(root + (n / root))

return root

> squareroot(9)
3.0
>squareroot(15)
3.872983346207417
>

4. Lập trình hướng đối tượng trong Python

4.1. Định nghĩa lớp

Python là một ngôn ngữ lập trình hướng đối tượng. Một trong những tính
năng của ngôn ngữ lập trình hướng đối tượng là cho phép lập trình viên
tạo ra các lớp mới để mô hình hóa dữ liệu cần thiết để giải quyết vấn
đề của mình.

Sử dụng các kiểu dữ liệu trừu tượng để mô tả về đối tượng dữ liệu


(trạng thái) và những gì có thể làm (các phương thức). Bằng cách xây
dựng một lớp thực hiện kiểu dữ liệu trừu tượng, lập trình viên có thể
tận dụng quá trình trừu tượng hóa và đồng thời cung cấp các chi tiết
cần thiết để sử dụng tính năng trừu tượng trong một chương trình.

Một cách tổng quát, định nghĩa một lớp mới bằng cách xác định tên và
tập các phương thức tương tự cú pháp định nghĩa hàm ở trên.

class FunctionName:

#the methods go here

4.2. Minh hoạ cài đặt “lớp phân số”

Ta minh hoạ cài đặt một lớp phân số - fraction.

Một phân số gồm hai phần: tử số - num (numerator), là một số nguyên


nào và mẫu số - den (denominator), là số nguyên lớn hơn 0 (phân số âm
là phân số có tử số âm). Ta sẽ biểu diễn phân số dưới dạng chính xác,
chẳng hạn 3/5 hay -7/12.

Các phép toán của fraction cần cho phép đối tượng dữ liệu fraction
hoạt động giống như bất kỳ giá trị số nào khác (int, float, …). Có thể
cộng, trừ, nhân và chia các phân số với nhau, và có thể hiển thị các
phân số dưới dạng quen, ví dụ 3/5. Hơn nữa, mọi phương thức của
fraction phải trả về kết quả ở dạng rút gọn.

Trước hết, ta định nghĩa một lớp mới fraction (phân số) này:

class fraction:

#the methods go here


Rồi, ta định nghĩa các phương thức. Phương thức đầu tiên mà tất cả các
lớp nên cung cấp là phương thức khởi tạo. Hàm contructor (tạo) xác
định cách thức các đối tượng dữ liệu được tạo. Để tạo đối tượng
fraction, ta cung cấp tử số (num) và mẫu số (den). Trong Python,
phương thức khởi tạo luôn được gọi là __init__ (hai dấu gạch dưới
trước và sau init) và được hiển thị trong

class fraction:

def __init__(self,top,bottom):

self.num = top
self.den = bottom
Lưu ý là danh sách tham số hình thức gồm (self, top, bottom). self là
một tham số đặc biệt sẽ luôn được sử dụng như một tham chiếu lại chính
đối tượng. self phải luôn là tham số hình thức đầu tiên; tuy nhiên,
self sẽ không bao giờ được cung cấp tham số thực khi gọi. Như đã mô tả
trước, phân số yêu cầu hai phần dữ liệu trạng thái, tử số (num) và mẫu
số (den). Ký hiệu self.num trong hàm tạo định nghĩa đối tượng phân số
có một đối tượng dữ liệu là num xem như một phần của trạng thái của
nó. Tương tự, self.den tạo ra mẫu số. Giá trị của hai tham số hình
thức ban đầu được gán cho trạng thái, cho phép đối tượng phân số mới
có giá trị khởi tạo.

Để tạo một thể hiện của lớp fraction, phải gọi hàm tạo bằng tên của
lớp và chuyển các giá trị thực cho trạng thái đầu (lưu ý rằng không
bao giờ gọi trực tiếp __init__). Ví dụ, lệnh

 myfraction = fraction(3,5)
tạo ra một đối tượng afraction biểu diễn cho 3/5 (ba phần năm).

Tiếp theo ta có thể thực hiện thao tác hay tính toán trên phân. Để bắt
đầu, hãy xem điều gì sẽ xảy ra khi ta in một đối tượng Phân số.

>myf = fraction(3,5)
>print(myf)
<__main__.fraction instance at 0x409b1acc>
Đối tượng fraction, tên myf, không đáp ứng yêu cầu hàm print. Hàm
print yêu cầu tham số của nó tự chuyển thành chuỗi để chuỗi có thể in
ra. Lựa chọn duy nhất mà myf có là hiển thị là tham chiếu, chính là
địa chỉ. Đây không phải là thứ ta muốn.

Có hai cách giải quyết vấn đề này. Một là định nghĩa phương thức, ta
gọi là show cho phép đối tượng fraction in ra dưới dạng một chuỗi. Ta
có thể cài đặt phương thức này như đoạn mã dưới. Nếu tạo một đối tượng
fraction như trên, ta có thể yêu cầu nó hiển thị chính nó, nói cách
khác, tự in ra ở dạng thích hợp. Thật không may, điều này không hoạt
động. Để hàm print hoạt động, ta cần cho lớp fraction biết cách chuyển
đổi chính nó thành một chuỗi (string), là tham số hàm print cần để
thực hiện. Ta coi:

def show(self):
print(self.num,"/",self.den)

> myf = fraction(3,5)


> myf.show()
3 / 5
> print(myf)
<__main__.Fraction instance at 0x40bce9ac>
>
Trong Python, mọi lớp (class) đều có một tập các phương thức chuẩn,
được cung cấp nhưng không hoạt động bình thường. Một trong những
phương thức này, __str__, là phương thức để chuyển một đối tượng thành
một chuỗi. Việc cài đặt mặc định cho phương thức này là trả về chuỗi
địa chỉ tương ứng. Ta có thể cài đặt “tốt hơn” phương thức này. Cài
đặt này ghi đè lên cài đặt có trước hoặc định nghĩa lại hành vi của
phương thức.

Để làm điều này, ta chỉ cần xác định một phương thức với tên __str__
và cài đặt lại như trong đoạn mã dưới. Định nghĩa này không cần bất kỳ
thông tin nào khác ngoại trừ tham số cụ thể. Đổi lại, phương pháp sẽ
xây dựng một biểu diễn chuỗi bằng cách chuyển từng phần dữ liệu trạng
thái bên trong sang chuỗi và sau đó đặt ký tự / vào giữa các chuỗi
bằng cách nối chuỗi. Chuỗi kết quả sẽ được trả về một đối tượng
fraction được yêu cầu chuyển dạng chính nó sang dạng chuỗi. Lưu ý các
cách khác nhau mà chức năng này được sử dụng trong ví dụ này.

def __str__(self):
return str(self.num)+"/"+str(self.den)
> myf = fraction(3,5)
> print(myf)
3/5
> print("I ate", myf, "of the pizza")
I ate 3/5 of the pizza
> myf.__str__()
'3/5'
> str(myf)
'3/5'
>
Ta sẽ tạo thêm một số phương thức khác cho lớp fraction của ta. Cụ thể
là các phép toán số học cơ bản. Ta tạo hai đối tượng Phân số và cộng
lại bằng cách sử dụng ký hiệu “+”thông thường. Khi chưa cài đặt "+",
ta thấy:

> f1 = Fraction(1,4)
> f2 = Fraction(1,2)
> f1+f2

Traceback (most recent call last):


File "<pyshell#173>", line 1, in -toplevel-
f1+f2
TypeError: unsupported operand type(s) for +:
'instance' and 'instance'
>

Toán tử “+” (hiện tại) không hiểu các toán hạng phân số. Ta khắc phục
điều bằng cách cung cấp cho lớp fraction một phương thức thay cho
phương thức cộng. Trong Python, phương thức này được gọi là __add__ và
nó yêu cầu hai tham số. Tham số thứ nhất, self, bắt buộc, và tham số
thứ hai là toán hạng khác trong biểu thức. Ví dụ,

f1.__add__(f2)
Lệnh trên yêu cầu đối tượng fraction f1 cộng thêm đối tượng fraction
f2 vào chính nó. Và viết theo cách thông thường f1 + f2.

Phương thức được cài đặt như sau: a/b + c/d = (ad+bc)/cd,

def __add__(self,otherfraction):

newnum = self.num*otherfraction.den + self.den*otherfraction.num


newden = self.den * otherfraction.den

return Fraction(newnum,newden)
> f1=Fraction(1,4)
> f2=Fraction(1,2)
> f3=f1+f2
> print(f3)
6/8
>
Kết quả sẽ không sai nhưng chưa phải phân số. Phân số phải ở dạng tối
giản, không phải 6/8 mà phải 3/4. Để rút gọn, ta chia tử và mẫu cho
ước chung lớn nhất của chúng. Thuật giải Euclid sẽ được cài đặt trong
hàm GCD.

def gcd(m,n):

while m%n != 0:

mm = m

nn = n

m = nn

n = mm % nn

return n

print(gcd(20,10))

Và ta viết lại phương thức cộng:

def __add__(self,otherfraction):
newnum = self.num*otherfraction.den + self.den*otherfraction.num
newden = self.den * otherfraction.den
common = gcd(newnum,newden)
return Fraction(newnum//common,newden//common)
> f1=fraction(1,4)
> f2=fraction(1,2)
> f3=f1+f2
> print(f3)
3/4
>
Để thực tập, các bạn có thể phát triển tiếp đoạn mã sau để cài đặt các
phương thức quen thuộc cho đối tượng Phân số để có một lớp Phân số
dùng được.
def gcd(m,n):

while m%n != 0:

mm = m

nn = n

m = nn

n = mm % nn

return n

class fraction:

def __init__(self,top,bottom):

self.num = top

self.den = bottom

def __str__(self):

return str(self.num)+"/"+str(self.den)

def show(self):

print(self.num,"/",self.den)

def __add__(self,otherfraction):

newnum = self.num*otherfraction.den + \

self.den*otherfraction.num

newden = self.den * otherfraction.den

common = gcd(newnum,newden)

return fraction(newnum//common,newden//common)

def __eq__(self, other):

firstnum = self.num * other.den


secondnum = other.num * self.den

return firstnum == secondnum

x = fraction(1,2)

y = fraction(2,3)

print(x+y)

print(x == y)

4.3. Các ví dụ về lớp

4.3.1. Lớp tập hợp - SETOF

Để nắm vững kỹ thuật lập trình hướng đối tượng dùng Python, bạn hãy
đọc và thử nghiệm đoạn mã cài đặt đối tượng quen thuộc trong toán. Sau
đó, phát triển hoàn chỉnh lớp tập hợp với các phép toán tổ hợp trong
lý thuyết tập hợp.

class SETOF:

def __init__(self,setinput):

X = []

for e in setinput:

if not (e in X):

X = X + [e]

self.set = X

def __add__(self, otherset):

newset = self.set

for e in otherset.set:

if not(e in newset):

newset = newset + [e]

return SETOF(newset)

def intersection(self,otherset):

newset=[]
for e in self.set:

if e in otherset.set:

newset = newset + [e]

return SETOF(newset)

A=SETOF(['cam','quyt','oi'])

B=SETOF([1,"cam",1])

C=A+B

D=A.intersection(B)

print("A = ", A.set)

print("B = ", B.set)

print("C = A U B =", C.set)

print("D = A /\ B =", D.set)

4.3.2. Lớp máy tính tay – calculator

Hãy đọc và thử nghiệm đoạn mã cài đặt máy tính tay đơn gian. Sau đó,
bạn có thể phát triển hoàn chỉnh lớp calculator để có một mý tính theo
ý bạn.

class calculator:

def __init__(self, string):

calculator.exp = string

def postfix(self):

S = [] #stack empty S

Expstring = self.exp

Exp = []

i = 0

n = len(Expstring)

while i < n:

if Expstring[i] in ['+', '-', '*', '/']:

Exp.append(Expstring[i])
i = i + 1

else:

num = 0

while (i < n) and (not(Expstring[i] in ['+', '-', '*', '/'])):

num = num*10 + int(Expstring[i])

i = i + 1

Exp.append(num)

postExp = []

for e in Exp:

if e in ['+','-','*','/']:

if S == []:

S.append(e)

else:

top = S[len(S)-1]

if top in ['+', '-']:

S.append(e)

else: #top in ['*', '/']

if e in ['*', '/']:

S.append(e)

else: #e in ['+', '-']

while (S!=[]) and (top in ['*', '/']):

postExp.append(S.pop())

S.append(e)

else:

postExp.append(e)

while S != []:

postExp.append(S.pop())

#return calculator(postExp)

S = []
for token in postExp:

if token in ['+', '-', '*', '/']:

t2 = S.pop()

t1 = S.pop()

if token == '+':

t3 = t1 + t2

elif token == '-':

t3 = t1 - t2

elif token == '*':

t3 = t1*t2

else:

if t2 == 0:

print("divide by zero...")

return

else:

t3 = t1//t2

S.append(t3)

else:

S.append(token)

return calculator(S.pop())

#main

infix = calculator("100+2-3*2-1")

print(infix.exp)

postfix = calculator.postfix(infix)

print(postfix.exp)

Tóm tắt

. Khoa học máy tính là ngành nghiên cứu giải quyết các vấn đề thực
tiễn đặt ra.
. Trừu tượng hoá là phương pháp biểu diễn cả tiến trình lẫn dữ liệu
(thuật giải + cấu trúc dữ liệu) trong cùng một đối tượng dữ liệu.

. Kiểu dữ liệu trừu tượng giúp quản lý độ phức tạp bằng cách che
giấucác chi tiết của dữ liệu.

. Python là một ngôn ngữ hướng đối tượng.

. Danh sách, bộ, và chuỗi được xây dựng trong cấu trúc tập hợp
(collection) tuyến tính của Python.

. Từ điển và tập hợp là dữ liệu tập hợp (collection) không tuyến tính.

. Lớp cho phép cài đặt các kiểu dữ liệu trừu tượng.

. Có thể ghi đè các phương thức chuẩn cũng như tạo ra các phương thức
mới.

You might also like