Python có bốn cấu trúc dữ liệu tích hợp được dùng nhiều nhất: list, tuple, dict và set. Chọn đúng cấu trúc không chỉ làm code gọn hơn mà còn ảnh hưởng trực tiếp tới tốc độ chương trình.
List — danh sách có thứ tự, sửa được
List là lựa chọn mặc định khi bạn cần một dãy phần tử theo thứ tự:
ngon_ngu = ["Python", "Go", "Rust"]
ngon_ngu.append("Java")
ngon_ngu.insert(0, "C")
ngon_ngu.remove("Go")
print(ngon_ngu) # ['C', 'Python', 'Rust', 'Java']
Cắt lát (slicing) là một trong những tính năng tiện nhất của Python:
so = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(so[2:5]) # [2, 3, 4]
print(so[:3]) # [0, 1, 2]
print(so[-3:]) # [7, 8, 9]
print(so[::2]) # [0, 2, 4, 6, 8]
print(so[::-1]) # đảo ngược danh sách
Một cái bẫy phổ biến
Đừng dùng list làm giá trị mặc định cho tham số hàm:
def them_vao(item, ds=[]): # SAI
ds.append(item)
return ds
print(them_vao(1)) # [1]
print(them_vao(2)) # [1, 2] — không phải [2]!
Giá trị mặc định được tạo một lần duy nhất khi hàm được định nghĩa, không phải mỗi lần gọi. Cách viết đúng:
def them_vao(item, ds=None):
if ds is None:
ds = []
ds.append(item)
return ds
Tuple — bất biến, nhẹ và an toàn
Tuple giống list nhưng không thể sửa sau khi tạo. Dùng tuple khi dữ liệu không nên thay đổi:
toa_do = (10.5, 20.3)
mau_do = (255, 0, 0)
Vì bất biến nên tuple có thể làm khóa của dict, còn list thì không:
khoang_cach = {
(0, 0): "gốc tọa độ",
(1, 1): "điểm A",
}
Tuple cũng dùng để trả về nhiều giá trị từ một hàm — một thói quen rất Python:
def phan_tich(ds):
return min(ds), max(ds), sum(ds) / len(ds)
nho_nhat, lon_nhat, trung_binh = phan_tich([4, 8, 15, 16, 23])
Dict — tra cứu theo khóa
Dict lưu các cặp khóa–giá trị và tra cứu gần như tức thì, bất kể dict lớn cỡ nào:
nhan_vien = {
"ten": "Hùng",
"tuoi": 28,
"phong_ban": "Kỹ thuật",
}
print(nhan_vien["ten"])
print(nhan_vien.get("luong", "chưa có")) # tránh KeyError
Dùng .get() khi bạn không chắc khóa có tồn tại. Truy cập bằng ngoặc vuông sẽ ném KeyError nếu khóa không có.
Duyệt dict đúng cách:
for khoa, gia_tri in nhan_vien.items():
print(f"{khoa}: {gia_tri}")
Từ Python 3.7, dict giữ nguyên thứ tự chèn — đây là hành vi được đảm bảo trong đặc tả ngôn ngữ, không còn là chi tiết cài đặt.
Đếm và gom nhóm
Khi cần đếm, đừng viết thủ công. Module collections có sẵn công cụ:
from collections import Counter, defaultdict
tu = "python là ngôn ngữ python rất python".split()
print(Counter(tu).most_common(2))
# [('python', 3), ('là', 1)]
nhom = defaultdict(list)
for ten, phong in [("An", "IT"), ("Bình", "HR"), ("Cường", "IT")]:
nhom[phong].append(ten)
print(dict(nhom))
# {'IT': ['An', 'Cường'], 'HR': ['Bình']}
Set — tập hợp không trùng lặp
Set tự động loại bỏ phần tử trùng và kiểm tra thành viên rất nhanh:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # hợp: {1, 2, 3, 4, 5, 6}
print(a & b) # giao: {3, 4}
print(a - b) # hiệu: {1, 2}
print(a ^ b) # đối xứng: {1, 2, 5, 6}
Khác biệt về tốc độ giữa set và list rất đáng kể khi kiểm tra phần tử có tồn tại hay không. Với danh sách một triệu phần tử, x in danh_sach phải duyệt tuần tự, còn x in tap_hop tra trực tiếp qua bảng băm.
ds = list(range(1_000_000))
tap = set(ds)
999_999 in ds # chậm — phải duyệt gần hết
999_999 in tap # nhanh — gần như tức thì
Chọn cấu trúc nào?
- List — dãy có thứ tự, cần thêm/xóa/sửa phần tử
- Tuple — dữ liệu cố định, làm khóa dict, trả nhiều giá trị
- Dict — tra cứu theo khóa, biểu diễn bản ghi có tên trường
- Set — loại trùng lặp, kiểm tra tồn tại, phép toán tập hợp
Một mẹo nhỏ: nếu bạn thấy mình viết if x in danh_sach bên trong một vòng lặp, hãy cân nhắc chuyển danh sách đó thành set trước vòng lặp. Đây là một trong những tối ưu đơn giản mà hiệu quả nhất.