Chọn đến phần học sinh cần nhanh chóng thông qua mục lục bằng cách click đến phần đó
- I. Lý thuyết trọng tâm
- 1. Thiết kế chương trình theo mô đun là gì?
- 2. Minh hoạ qua bài toán quản lí cửa hàng
- 3. Lợi ích của thiết kế theo mô đun
- II. Phương pháp giải các dạng bài tập
- Dạng 1: Nâng cấp mô đun có sẵn
- Dạng 2: Viết mô đun mới
- Dạng 3: Xác định thuật toán phù hợp cho mô đun
- III. Sơ đồ tư duy
I. Lý thuyết trọng tâm
1. Thiết kế chương trình theo mô đun là gì?
Ở các bài trước, ta đã biết phương pháp làm mịn dần – chi tiết hoá ý tưởng qua nhiều bước cho đến khi có code hoàn chỉnh. Phương pháp thiết kế theo mô đun là bước phát triển tiếp theo: thay vì viết tất cả vào một chương trình dài, ta tách bài toán lớn thành các bài toán nhỏ hơn (gọi là các mô đun), mỗi mô đun đảm nhận một chức năng riêng biệt và tương đối độc lập.
Mỗi mô đun thường được viết thành một hàm (hoặc thủ tục) riêng. Chương trình chính chỉ đơn giản là ghép nối các hàm lại theo đúng thứ tự.
2. Minh hoạ qua bài toán quản lí cửa hàng
Bài toán: Em được giao quản lí một cửa hàng bán thực phẩm. Hằng ngày cần nhập danh sách mặt hàng và doanh số bán hàng vào tệp Data.inp (mỗi dòng gồm tên mặt hàng và doanh số, cách nhau bằng dấu phẩy). Cuối ngày cần tạo báo cáo Data.out gồm 6 dòng: 3 mặt hàng có doanh số cao nhất và 3 mặt hàng có doanh số thấp nhất.
Ví dụ dữ liệu:
Data.inp Data.out
Cà rốt, 1350 Gạo thường 23124
Khoai tây, 4400 Gạo ST25 19221
Hành tươi, 1367.5 Cam 9800
Bắp cải, 3400 Khoai lang 2100
Cà chua, 5609 Hành tươi 1367.5
Khoai lang, 2100 Cà rốt 1350
Gạo ST25, 19221
Gạo thường, 23124
Cam, 9800
Chuối, 7823

Bước 1 – Thiết kế chung: chia thành các mô đun
Phân tích bài toán, ta nhận thấy có thể chia thành ba công việc chính, tương đối độc lập:
| Mô đun | Chức năng | Hàm tương ứng |
|---|---|---|
| Nhập dữ liệu | Đọc tên mặt hàng và doanh số từ tệp Data.inp |
NhapDL(fin) |
| Xử lí dữ liệu | Sắp xếp các mặt hàng theo doanh số tăng dần | Sapxep(A, B) |
| Xuất báo cáo | Ghi 3 mặt hàng cao nhất và 3 thấp nhất ra tệp Data.out |
GhiDL(P, S, fout) |
Chương trình chính chỉ gồm vài dòng gọi các hàm:
fin = "Data.inp"
fout = "Data.out"
P, S = NhapDL(fin)
Sapxep(S, P)
GhiDL(P, S, fout)
Rất ngắn gọn và dễ hiểu! Giờ ta thiết kế chi tiết từng mô đun.
Bước 2 – Mô đun nhập dữ liệu: NhapDL(fin)
Hàm này mở tệp fin, đọc từng dòng, tách tên mặt hàng và doanh số ra hai danh sách riêng (P chứa tên, S chứa doanh số), rồi trả về cả hai.
def NhapDL(fin):
f = open(fin, encoding="UTF-8")
P = []
S = []
for line in f:
A = line.split(",")
P.append(A[0])
S.append(float(A[1]))
f.close()
return P, S
Giải thích: Mỗi dòng tệp có dạng "Cà rốt, 1350". Lệnh split(",") tách thành hai phần: A[0] = "Cà rốt" (tên) và A[1] = " 1350" (doanh số dạng chuỗi). Hàm float() chuyển doanh số thành số thực.
Bước 3 – Mô đun xử lí dữ liệu: Sapxep(A, B)
Hàm sắp xếp hai mảng A và B theo thứ tự tăng dần của A. Khi đổi chỗ phần tử trong A, đồng thời đổi chỗ phần tử tương ứng trong B (kỹ thuật sắp xếp song song đã gặp ở Bài 22).
def Sapxep(A, B):
n = len(A)
for i in range(1, n):
j = i
while j > 0 and A[j] < A[j - 1]:
A[j], A[j - 1] = A[j - 1], A[j]
B[j], B[j - 1] = B[j - 1], B[j]
j = j - 1
Giải thích: Sử dụng thuật toán sắp xếp chèn (phiên bản đổi chỗ). Khi gọi Sapxep(S, P) trong chương trình chính, dãy S (doanh số) được sắp tăng dần, đồng thời dãy P (tên mặt hàng) cũng được đổi chỗ theo → tên luôn đi kèm đúng doanh số.
Bước 4 – Mô đun xuất báo cáo: GhiDL(P, S, fout)
Sau khi sắp xếp tăng dần, 3 phần tử cuối dãy có doanh số cao nhất, 3 phần tử đầu dãy có doanh số thấp nhất. Hàm ghi các phần tử này ra tệp.
def GhiDL(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
for i in range(n - 1, n - 4, -1):
print(P[i], S[i], file=f)
for i in range(2, -1, -1):
print(P[i], S[i], file=f)
f.close()
Giải thích:
range(n-1, n-4, -1)duyệt 3 chỉ số cuối: n−1, n−2, n−3 (3 mặt hàng doanh số cao nhất, từ cao xuống).range(2, -1, -1)duyệt 3 chỉ số đầu: 2, 1, 0 (3 mặt hàng doanh số thấp nhất, từ cao xuống thấp nhất).print(..., file=f)ghi ra tệp thay vì hiển thị trên màn hình.
Chương trình hoàn chỉnh
def NhapDL(fin):
f = open(fin, encoding="UTF-8")
P = []
S = []
for line in f:
A = line.split(",")
P.append(A[0])
S.append(float(A[1]))
f.close()
return P, S
def Sapxep(A, B):
n = len(A)
for i in range(1, n):
j = i
while j > 0 and A[j] < A[j - 1]:
A[j], A[j - 1] = A[j - 1], A[j]
B[j], B[j - 1] = B[j - 1], B[j]
j = j - 1
def GhiDL(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
for i in range(n - 1, n - 4, -1):
print(P[i], S[i], file=f)
for i in range(2, -1, -1):
print(P[i], S[i], file=f)
f.close()
# Chương trình chính
fin = "Data.inp"
fout = "Data.out"
P, S = NhapDL(fin)
Sapxep(S, P)
GhiDL(P, S, fout)
Chương trình gồm 3 mô đun (3 hàm) và phần chương trình chính chỉ có 5 dòng. Mỗi mô đun có thể được phát triển, kiểm tra và sửa lỗi độc lập với các mô đun khác.
3. Lợi ích của thiết kế theo mô đun
Để thấy rõ lợi ích, hãy xem điều gì xảy ra khi cần thay đổi hoặc bổ sung yêu cầu:
Công việc bổ sung 1: Thay đổi nội dung báo cáo
Yêu cầu mới: Ghi 2 mặt hàng doanh số cao nhất và 4 mặt hàng doanh số thấp nhất.
Chỉ cần sửa hàm GhiDL(), thay đổi hai dòng lệnh (chỉnh phạm vi duyệt). Các mô đun NhapDL() và Sapxep() giữ nguyên hoàn toàn.
def GhiDL(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
for i in range(n - 1, n - 3, -1): # Đổi n-4 → n-3 (chỉ lấy 2)
print(P[i], S[i], file=f)
for i in range(3, -1, -1): # Đổi 2 → 3 (lấy 4 mặt hàng)
print(P[i], S[i], file=f)
f.close()
→ Sửa cục bộ trong một mô đun, không ảnh hưởng phần còn lại.
Công việc bổ sung 2: Cập nhật mặt hàng mới
Chỉ cần mở tệp Data.inp và thêm dòng mới. Không cần sửa chương trình vì mô đun NhapDL() đã được thiết kế đọc bao nhiêu dòng cũng được.
→ Công việc không liên quan đến thuật toán và lập trình.
Công việc bổ sung 3: Thêm báo cáo mới
Yêu cầu: Tạo thêm tệp Data2.out ghi doanh số trung bình và danh sách mặt hàng có doanh số lớn hơn trung bình.
Viết hàm mới hoàn toàn – BC2(P, S, fout) – không cần sửa bất kỳ mô đun nào đã có:
def BC2(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
average = sum(S) / n
print("Doanh số trung bình:", average, file=f)
for i in range(n):
if S[i] > average:
print(P[i], S[i], file=f)
f.close()
Trong chương trình chính chỉ cần thêm một dòng:
BC2(P, S, "Data2.out")
→ Bổ sung mô đun mới dễ dàng, không ảnh hưởng code cũ.
Tổng kết lợi ích
| Ưu điểm | Giải thích |
|---|---|
| Ngắn gọn, dễ hiểu | Chương trình chính chỉ vài dòng gọi hàm, ai cũng đọc hiểu được luồng xử lí |
| Dùng lại nhiều lần | Hàm Sapxep() có thể dùng cho bất kỳ bài sắp xếp nào, không chỉ bài này |
| Dễ nâng cấp | Sửa một mô đun không ảnh hưởng các mô đun khác |
| Dễ bổ sung | Thêm chức năng mới = thêm hàm mới + gọi trong chương trình chính |
| Hỗ trợ làm việc nhóm | Mỗi người phụ trách một mô đun, phát triển song song |
II. Phương pháp giải các dạng bài tập
Dạng 1: Nâng cấp mô đun có sẵn
Phương pháp: Xác định mô đun nào cần thay đổi, sửa cục bộ trong mô đun đó, giữ nguyên phần còn lại.
Bài 1 (Luyện tập SGK). Nếu công việc bổ sung 3 có thêm yêu cầu in ra số lượng mặt hàng đã bán trong ngày thì cần thêm hay sửa lệnh nào của chương trình BC2()?
Lời giải:
Số lượng mặt hàng đã bán trong ngày chính là tổng số dòng trong tệp Data.inp, tức là độ dài của mảng P (hoặc S). Giá trị này đã có sẵn trong biến n = len(P).
Chỉ cần thêm một dòng lệnh print vào hàm BC2(), ngay sau dòng tính trung bình:
def BC2(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
average = sum(S) / n
print("Số lượng mặt hàng đã bán:", n, file=f) # Dòng thêm mới
print("Doanh số trung bình:", average, file=f)
for i in range(n):
if S[i] > average:
print(P[i], S[i], file=f)
f.close()
Giải thích: Không cần sửa mô đun nào khác. Biến n đã được tính sẵn (len(P) = số mặt hàng), chỉ việc in thêm ra tệp. Đây minh hoạ cho lợi ích dễ nâng cấp của thiết kế mô đun.
Dạng 2: Viết mô đun mới
Phương pháp: Tạo hàm mới thực hiện chức năng được yêu cầu, sử dụng dữ liệu từ các mô đun đã có (P, S), rồi gọi hàm mới trong chương trình chính.
Bài 2 (Luyện tập SGK). Viết thêm chương trình cho công việc bổ sung 4: Cần in ra danh sách 1/3 số mặt hàng có doanh số thấp nhất trong ngày.
Lời giải:
Sau khi sắp xếp tăng dần, 1/3 số mặt hàng doanh số thấp nhất nằm ở đầu dãy. Số lượng cần in = n // 3 (chia lấy phần nguyên).
def BC_ThapNhat(P, S, fout):
f = open(fout, "w", encoding="UTF-8")
n = len(P)
so_luong = n // 3
print(f"Danh sách 1/3 mặt hàng doanh số thấp nhất ({so_luong} mặt hàng):", file=f)
for i in range(so_luong):
print(P[i], S[i], file=f)
f.close()
Trong chương trình chính thêm:
BC_ThapNhat(P, S, "ThapNhat.out")
Giải thích: Vì dãy đã được sắp xếp tăng dần (bởi mô đun Sapxep ở trước), nên so_luong phần tử đầu tiên chính là các mặt hàng doanh số thấp nhất. Lệnh n // 3 đảm bảo kết quả là số nguyên (ví dụ 10 mặt hàng thì lấy 3).
Dạng 3: Xác định thuật toán phù hợp cho mô đun
Bài 3 (Vận dụng SGK). Cho trước số K (doanh số giả định), cần tìm mặt hàng có doanh số nhỏ hơn K nhưng gần K nhất. Bài toán này có thể sử dụng thuật toán tìm kiếm nào?
Lời giải:
Vì dãy S đã được sắp xếp tăng dần (bởi mô đun Sapxep), ta có thể sử dụng thuật toán tìm kiếm nhị phân để tìm vị trí chèn của K, từ đó xác định mặt hàng có doanh số nhỏ hơn K và gần K nhất.
def TimGanK(P, S, K):
"""Tìm mặt hàng có doanh số nhỏ hơn K nhưng gần K nhất.
Dãy S đã sắp xếp tăng dần."""
# Tìm kiếm nhị phân: tìm vị trí lớn nhất mà S[pos] < K
left = 0
right = len(S) - 1
pos = -1
while left <= right:
mid = (left + right) // 2
if S[mid] < K:
pos = mid
left = mid + 1
else:
right = mid - 1
if pos == -1:
print("Không có mặt hàng nào có doanh số nhỏ hơn", K)
else:
print(f"Mặt hàng gần K={K} nhất: {P[pos]}, doanh số: {S[pos]}")
Trong chương trình chính:
K = float(input("Nhập doanh số giả định K: "))
TimGanK(P, S, K)
Giải thích: Tìm kiếm nhị phân phù hợp vì dãy S đã sắp xếp. Biến pos lưu vị trí phần tử lớn nhất mà S[pos] < K. Do dãy tăng dần, phần tử tại pos chính là phần tử nhỏ hơn K và gần K nhất. Tìm kiếm nhị phân chạy rất nhanh (thời gian log₂(n)) so với tìm kiếm tuần tự.
Bài 4 (Vận dụng SGK). Một công ti du lịch có n địa điểm tham quan đánh số 0, 1, …, n−1. Khách hàng đánh giá từng địa điểm bằng điểm số (dương, âm hoặc 0). Cần tìm tour gồm các vị trí liên tục từ i đến j sao cho tổng điểm đánh giá là lớn nhất.
Ví dụ: Dãy đánh giá [1, −3, 4, 10, 0, −5, −8, 2, −1, 7, 2] → Tour tối ưu cần tìm.
Lời giải:
Đây là bài toán kinh điển tìm dãy con liên tục có tổng lớn nhất. Ta thiết kế theo mô đun.
Mô đun 1 – Nhập dữ liệu:
def NhapDanhGia(fin):
f = open(fin, encoding="UTF-8")
line = f.readline()
A = [int(x) for x in line.split(",")]
f.close()
return A
Mô đun 2 – Tìm tour tối ưu:
Ý tưởng: duyệt tất cả các đoạn liên tục (i, j), tính tổng mỗi đoạn, ghi nhận đoạn có tổng lớn nhất.
def TimTour(A):
n = len(A)
tong_max = A[0]
vi_tri_bat_dau = 0
vi_tri_ket_thuc = 0
for i in range(n):
tong = 0
for j in range(i, n):
tong = tong + A[j]
if tong > tong_max:
tong_max = tong
vi_tri_bat_dau = i
vi_tri_ket_thuc = j
return vi_tri_bat_dau, vi_tri_ket_thuc, tong_max
Mô đun 3 – Xuất kết quả:
def XuatTour(A, bat_dau, ket_thuc, tong):
print(f"Tour tối ưu: từ địa điểm {bat_dau} đến {ket_thuc}")
print(f"Các địa điểm: {A[bat_dau:ket_thuc + 1]}")
print(f"Tổng điểm đánh giá: {tong}")
Chương trình chính:
A = NhapDanhGia("tour.inp")
bat_dau, ket_thuc, tong = TimTour(A)
XuatTour(A, bat_dau, ket_thuc, tong)
Với dãy [1, −3, 4, 10, 0, −5, −8, 2, −1, 7, 2], kết quả:
Tour tối ưu: từ địa điểm 2 đến 3
Các địa điểm: [4, 10]
Tổng điểm đánh giá: 14
Giải thích: Mô đun TimTour duyệt mọi đoạn liên tục bằng hai vòng for lồng nhau. Vòng ngoài chọn điểm bắt đầu i, vòng trong mở rộng đến j và cộng dồn tổng. Mỗi khi tổng vượt tong_max, cập nhật kết quả tốt nhất. Chương trình gồm 3 mô đun rõ ràng: nhập – xử lí – xuất, có thể phát triển hoặc thay đổi từng phần độc lập.
III. Sơ đồ tư duy
┌─ Mô đun 1: Nhập dữ liệu
│ (đọc tệp → mảng P, S)
│
THIẾT KẾ ┌───┤─ Mô đun 2: Xử lí dữ liệu
THEO MÔ ĐUN ─────┤ │ (sắp xếp, tính toán)
│ │
│ └─ Mô đun 3: Xuất báo cáo
│ (ghi kết quả ra tệp)
│
│ Chương trình chính = ghép nối các mô đun
│
└─ LỢI ÍCH:
├─ Ngắn gọn, dễ hiểu
├─ Dùng lại nhiều lần
├─ Dễ nâng cấp (sửa 1 mô đun, không ảnh hưởng)
├─ Dễ bổ sung mô đun mới
└─ Hỗ trợ làm việc nhóm
KHI CẦN THAY ĐỔI:
┌─ Nâng cấp mô đun cũ → sửa nội bộ 1 hàm
├─ Thêm chức năng mới → viết hàm mới + gọi trong main
└─ Thêm dữ liệu → sửa tệp đầu vào, không sửa code

Thầy Phạm Thành Danh
(Người kiểm duyệt, ra đề)
Chức vụ: Trưởng ban biên soạn môn Tin Học THPT
Trình độ: Cử nhân Sư phạm Tin học, Chứng chỉ hạng II, Chứng chỉ Tin học, Ngoại ngữ B1
Kinh nghiệm: 8+ năm kinh nghiệm tại Trường THPT Thuận Hóa
