1. Mục tiêu
- Hiểu và sử dụng kiểu dữ liệu dict để biểu diễn các bản ghi (records) và các cấu trúc dữ liệu phức hợp.
- Nắm được cú pháp cơ bản của class và hiểu được vai trò của nó trong việc định nghĩa các kiểu dữ liệu mới, phục vụ cho các bài toán có độ phức tạp cao hơn.
- Vận dụng dict và class để giải quyết các bài toán quản lý dữ liệu cơ bản trong lập trình thi đấu.
2. Giới thiệu và phạm vi ứng dụng
Trong các bài toán thực tế, dữ liệu thường không tồn tại ở dạng đơn lẻ (như một con số hay một chuỗi ký tự) mà ở dạng các cấu trúc có tổ chức. Ví dụ, thông tin của một học sinh bao gồm nhiều thành phần liên quan đến nhau: họ tên, mã số, điểm số. Một thực thể như vậy được gọi là một bản ghi (record).
Ngôn ngữ Python cung cấp các công cụ mạnh mẽ để làm việc với dữ liệu có cấu trúc. Trong đó, dictionary (viết tắt là dict) là một kiểu dữ liệu cực kỳ linh hoạt và được sử dụng rộng rãi trong lập trình thi đấu nhờ cú pháp gọn nhẹ. Bên cạnh đó, khái niệm class (lớp) cho phép định nghĩa các kiểu dữ liệu hoàn toàn mới, mang lại tính cấu trúc và khả năng mở rộng cao hơn, phù hợp cho các bài toán phức tạp.
Bài học này sẽ tập trung vào việc sử dụng dict như một phương tiện chính để tạo và quản lý các bản ghi, đồng thời giới thiệu sơ lược về class như một giải pháp thay thế cao cấp hơn.
3. Cú pháp và các ví dụ minh họa
3.1. Kiểu dữ liệu Dictionary (dict)
dict là một cấu trúc dữ liệu lưu trữ các cặp key: value (khóa: giá trị). Mỗi key là duy nhất và được dùng để truy xuất value tương ứng.
3.1.1. Cấu trúc cú pháp chuẩn
- Khởi tạo: Một dict rỗng có thể được tạo bằng {} hoặc dict(). Một dict có giá trị ban đầu được tạo bằng cú pháp {key1: value1, key2: value2, …}.
- Truy cập phần tử: Sử dụng cú pháp ten_dict[key] để lấy value tương ứng với key.
- Thêm mới / Cập nhật: Sử dụng phép gán ten_dict[key] = new_value. Nếu key chưa tồn tại, một cặp key: value mới sẽ được thêm vào. Nếu key đã tồn tại, value của nó sẽ được cập nhật.
- Xóa phần tử: Sử dụng từ khóa del ten_dict[key].
3.1.2. Ví dụ 1: Quản lý thông tin một học sinh
Để lưu trữ thông tin của một học sinh, ta có thể dùng dict với các key là tên các trường thông tin (ví dụ: “ho_ten”, “ma_sv”, “diem_tb”) và value là giá trị tương ứng.
# Khởi tạo một dictionary để lưu thông tin học sinh
hoc_sinh = {
"ho_ten": "Nguyễn Văn An",
"ma_sv": "21T1020001",
"diem_tb": 8.5
}
# Truy cập và in thông tin
print("Tên học sinh:", hoc_sinh["ho_ten"])
print("Điểm trung bình:", hoc_sinh["diem_tb"])
# Cập nhật điểm
hoc_sinh["diem_tb"] = 8.8
print("Điểm trung bình mới:", hoc_sinh["diem_tb"])
# Thêm thông tin mới (ví dụ: lớp)
hoc_sinh["lop"] = "21T-TIN"
print("Thông tin đầy đủ:", hoc_sinh)3.1.3. Ví dụ 2: Quản lý danh sách nhiều học sinh
Trong các bài toán, ta thường phải xử lý một tập hợp gồm nhiều bản ghi. Một cách tiếp cận phổ biến là sử dụng một danh sách (list) chứa các dict.
# Khởi tạo một danh sách (list) chứa các dictionary
danh_sach_lop = [
{
"ho_ten": "Nguyễn Văn An",
"ma_sv": "21T1020001",
"diem_tb": 8.5
},
{
"ho_ten": "Trần Thị Bình",
"ma_sv": "21T1020002",
"diem_tb": 9.2
},
{
"ho_ten": "Lê Văn Cường",
"ma_sv": "21T1020003",
"diem_tb": 7.8
}
]
# Duyệt qua danh sách và in thông tin các học sinh có điểm >= 8.0
print("Danh sách học sinh giỏi:")
for hs in danh_sach_lop:
if hs["diem_tb"] >= 8.0:
print(f"- {hs['ho_ten']}, Điểm: {hs['diem_tb']}")3.2. Giới thiệu sơ lược về Lớp (class)
class là một “bản thiết kế” cho phép tạo ra các đối tượng (objects). Mỗi đối tượng sẽ có các thuộc tính (attributes) và phương thức (methods) được định nghĩa trong lớp.
3.2.1. Khi nào cần sử dụng class?
Khi một bản ghi không chỉ chứa dữ liệu mà còn cần các hành vi (hàm) đi kèm, class là lựa chọn phù hợp. Ví dụ, một đối tượng HocSinh có thể có phương thức xep_loai() để tự tính toán xếp loại dựa trên điểm số của mình. Trong lập trình thi đấu, dict thường đủ dùng cho các bài toán lưu trữ dữ liệu đơn thuần. Tuy nhiên, với các bài toán mô phỏng hoặc cấu trúc dữ liệu phức tạp, class tỏ ra vượt trội.
3.2.2. Cấu trúc cú pháp cơ bản
Cú pháp cơ bản bao gồm từ khóa class, tên lớp, và phương thức khởi tạo __init__. Phương thức __init__ được tự động gọi khi một đối tượng mới được tạo ra, dùng để gán các giá trị ban đầu cho thuộc tính của đối tượng.
class TenLop:
def __init__(self, tham_so_1, tham_so_2):
# self tham chiếu đến chính đối tượng đang được tạo
self.thuoc_tinh_1 = tham_so_1
self.thuoc_tinh_2 = tham_so_23.2.3. Ví dụ: Định nghĩa lớp HocSinh
Cùng bài toán quản lý thông tin học sinh, ta có thể triển khai bằng class như sau.
class HocSinh:
def __init__(self, ho_ten, ma_sv, diem_tb):
# Gán các giá trị đầu vào cho các thuộc tính của đối tượng
self.ho_ten = ho_ten
self.ma_sv = ma_sv
self.diem_tb = diem_tb
# Tạo ra các đối tượng (instances) từ lớp HocSinh
hs1 = HocSinh("Nguyễn Văn An", "21T1020001", 8.5)
hs2 = HocSinh("Trần Thị Bình", "21T1020002", 9.2)
# Truy cập thuộc tính của đối tượng bằng dấu chấm (.)
print("Tên học sinh 1:", hs1.ho_ten)
print("Mã sinh viên 2:", hs2.ma_sv)
print("Điểm của học sinh 2:", hs2.diem_tb)4. Trực quan hóa và gỡ lỗi với Thonny
Môi trường Thonny cung cấp một công cụ gỡ lỗi trực quan, rất hữu ích để hiểu cách các cấu trúc dữ liệu như dict và đối tượng class được lưu trữ trong bộ nhớ.
- Với Dictionary: Khi chạy chương trình ở chế độ gỡ lỗi (Debug), cửa sổ “Variables” của Thonny sẽ hiển thị biến dict. Người dùng có thể nhấn vào để xem chi tiết các cặp
key: valuebên trong, giúp kiểm tra xem dữ liệu đã được lưu trữ đúng hay chưa. - Với Class: Khi một đối tượng được tạo từ một lớp, cửa sổ “Variables” sẽ hiển thị nó như một instance của lớp đó (ví dụ:
hs1 (HocSinh instance)). Người dùng có thể mở rộng để xem tất cả các thuộc tính (ho_ten, ma_sv, diem_tb) và giá trị tương ứng của chúng tại mỗi bước thực thi của chương trình.
5. Bài tập vận dụng
5.1. Bài tập 1: Từ điển Anh-Việt
5.1.1. Đề bài
Xây dựng một chương trình từ điển Anh-Việt đơn giản.
- Đầu vào: Dòng đầu tiên chứa số nguyên N. N dòng tiếp theo, mỗi dòng chứa một từ tiếng Anh và nghĩa tiếng Việt tương ứng, cách nhau bởi dấu hai chấm (ví dụ: hello:xin chào). Sau N dòng này là một dòng chứa một từ tiếng Anh cần tra.
- Đầu ra: In ra nghĩa tiếng Việt của từ cần tra. Nếu từ không có trong từ điển, in ra “Không tìm thấy”.
5.1.2. Lời giải và Phân tích
Phân tích:
Bài toán này yêu cầu một cơ chế tra cứu nhanh từ một “khóa” (từ tiếng Anh) đến một “giá trị” (nghĩa tiếng Việt). Cấu trúc dict là lựa chọn lý tưởng cho nhiệm vụ này, vì nó cho phép truy cập giá trị thông qua khóa với độ phức tạp trung bình là O(1).
Cài đặt:
# Đọc số lượng từ trong từ điển
n = int(input())
# Khởi tạo một dictionary rỗng để lưu từ điển
tu_dien = {}
# Đọc N dòng để xây dựng từ điển
for _ in range(n):
line = input().split(':')
tu_tieng_anh = line[0].strip()
nghia_tieng_viet = line[1].strip()
tu_dien[tu_tieng_anh] = nghia_tieng_viet
# Đọc từ cần tra
tu_can_tra = input().strip()
# Sử dụng phương thức get() để tra từ một cách an toàn
# get(key, default_value) sẽ trả về giá trị của key nếu key tồn tại,
# ngược lại sẽ trả về default_value.
nghia = tu_dien.get(tu_can_tra, "Không tìm thấy")
# In kết quả
print(nghia)5.2. Bài tập 2: Sắp xếp danh sách vận động viên
5.2.1. Đề bài
Viết chương trình quản lý và sắp xếp danh sách các vận động viên dựa trên thành tích.
- Đầu vào: Dòng đầu tiên chứa số nguyên N. N dòng tiếp theo, mỗi dòng chứa thông tin của một vận động viên gồm tên và thành tích (số giây), cách nhau bởi dấu phẩy.
- Đầu ra: In ra danh sách các vận động viên đã được sắp xếp theo thành tích tăng dần (thời gian ít hơn là tốt hơn). Mỗi vận động viên được in trên một dòng theo định dạng “Tên: [tên], Thành tích: [thành tích] giây”.
5.2.2. Lời giải và Phân tích
Phân tích:
Đầu tiên, ta cần lưu trữ thông tin của mỗi vận động viên (tên và thành tích) dưới dạng một bản ghi. Ta có thể sử dụng một danh sách các dict để làm điều này.
Tiếp theo, bài toán yêu cầu sắp xếp danh sách này. Hàm sort() hoặc sorted() của Python có một tham số key, cho phép chỉ định một hàm để trích xuất “khóa so sánh” từ mỗi phần tử.
Trong trường hợp này, khóa so sánh chính là thành tích của vận động viên. Ta có thể sử dụng một hàm lambda để thực hiện việc này một cách ngắn gọn.
Cài đặt:
# Đọc số lượng vận động viên
n = int(input())
# Khởi tạo danh sách để lưu thông tin
danh_sach_vdv = []
# Đọc thông tin N vận động viên
for _ in range(n):
line = input().split(',')
ten = line[0].strip()
# Chuyển thành tích sang kiểu số để có thể so sánh
thanh_tich = int(line[1].strip())
# Tạo một dict cho mỗi vận động viên và thêm vào danh sách
danh_sach_vdv.append({
"ten": ten,
"thanh_tich": thanh_tich
})
# Sắp xếp danh sách dựa trên giá trị của key "thanh_tich"
# lambda vdv: vdv["thanh_tich"] là một hàm ngắn gọn
# nhận đầu vào là một dict vdv và trả về giá trị của vdv["thanh_tich"]
danh_sach_vdv.sort(key=lambda vdv: vdv["thanh_tich"])
# In ra kết quả đã sắp xếp
for vdv in danh_sach_vdv:
print(f"Tên: {vdv['ten']}, Thành tích: {vdv['thanh_tich']} giây")6. Các lưu ý và lỗi thường gặp
- Lỗi KeyError: Đây là lỗi phổ biến nhất khi làm việc với dict. Lỗi này xảy ra khi cố gắng truy cập một key không tồn tại. Để tránh lỗi này, có thể kiểm tra sự tồn tại của key bằng toán tử in (if key in my_dict: …) hoặc sử dụng phương thức my_dict.get(key), phương thức này sẽ trả về None (hoặc một giá trị mặc định được chỉ định) nếu key không tồn tại thay vì gây ra lỗi.
- Khóa của Dictionary phải là đối tượng bất biến (immutable): Các key trong dict phải là các kiểu dữ liệu không thể thay đổi được nội dung sau khi tạo, ví dụ như số (int, float), chuỗi (str), hoặc tuple. Việc sử dụng một danh sách (list) hoặc một dict khác làm key sẽ gây ra lỗi TypeError.
- Phân biệt dict và class: Trong lập trình thi đấu, ưu tiên sử dụng dict khi cần một giải pháp nhanh gọn để lưu trữ và truy xuất dữ liệu có cấu trúc. Sử dụng class khi bài toán yêu cầu mô phỏng các đối tượng có cả dữ liệu (thuộc tính) và hành vi (phương thức) phức tạp.
7. Tổng kết
Kiểu bản ghi là một khái niệm nền tảng trong khoa học máy tính, cho phép biểu diễn các đối tượng trong thế giới thực một cách có cấu trúc.
- Dictionary trong Python là một công cụ cực kỳ hiệu quả và linh hoạt để triển khai kiểu bản ghi, đặc biệt phù hợp với các bài toán trong lập trình thi đấu nhờ cú pháp đơn giản và tốc độ truy xuất nhanh.
- Class cung cấp một phương pháp tiếp cận có cấu trúc và mạnh mẽ hơn, cho phép định nghĩa các kiểu dữ liệu mới hoàn chỉnh với cả thuộc tính và phương thức.
Việc thành thạo cả hai công cụ này sẽ giúp người lập trình lựa chọn được cấu trúc dữ liệu phù hợp nhất cho từng bài toán cụ thể.



