Trong bài này
Lan quản lý một câu lạc bộ có 3 bàn học. Mỗi bàn có một vị trí hàng, cột và một nhóm học viên. Lan muốn biết những tên nào xuất hiện, đếm số lượt và tìm nhanh nhóm của từng bạn. Python có các kiểu dữ liệu phù hợp cho từng việc đó.
Tuple giữ các giá trị theo thứ tự
Một vị trí trên lưới có thể được ghi bằng hai số, hàng trước và cột sau:
vi_tri = (2, 4)
print(vi_tri[0])
print(vi_tri[1])2 4
(2, 4) là một bộ giá trị (tuple), chứa hai phần tử theo thứ tự. Giống list, chỉ số bắt đầu từ 0. Dấu ngoặc tròn giúp nhận ra nhóm giá trị. Phép tạo một tuple từ nhiều giá trị được gọi là đóng gói (packing).
Bạn có thể lấy cả hai phần tử ra thành hai biến bằng mở gói (unpacking):
vi_tri = (2, 4)
hang, cot = vi_tri
print(hang)
print(cot)2 4
Số biến phải khớp số phần tử. hang nhận phần tử đầu, cot nhận phần tử sau. Đây cũng là cách gán nhiều giá trị trả về từ hàm đã học ở Bài 8.
Bộ giá trị(2, 4)Biến đầuhang nhận 2Biến saucot nhận 4
vi_tri = (2, 4)
hang, cot, tang = vi_triLỗi khi chạy, ở dòng 2
ValueError: not enough values to unpack (expected 3, got 2)
Python báo ValueError vì hai giá trị không đủ cho ba biến. Kiểm tra số phần tử trước khi mở gói.
Dấu phẩy xác định tuple một phần tử. Ngoặc đơn quanh một số chỉ nhóm biểu thức:
mot_ban = (5,)
mot_so = (5)
print(type(mot_ban))
print(type(mot_so))<class 'tuple'> <class 'int'>
Tuple không cho thay trực tiếp phần tử sau khi tạo. Bạn có thể tạo tuple mới khi cần vị trí khác:
vi_tri = (2, 4)
vi_tri[0] = 3Lỗi khi chạy, ở dòng 2
TypeError: 'tuple' object does not support item assignment
Python báo TypeError. List phù hợp hơn nếu bạn cần thay từng phần tử thường xuyên. Tuple gồm các số hoặc chuỗi có thể làm khóa dictionary; bạn sẽ gặp ứng dụng đó sau khi học dictionary.
Hai vị trí có cùng tọa độ theo cùng thứ tự được xem là bằng nhau:
ban_a = (2, 4)
ban_b = (2, 4)
ban_c = (4, 2)
print(ban_a == ban_b)
print(ban_a == ban_c)True False
Thứ tự quan trọng: hàng 2, cột 4 khác hàng 4, cột 2. Bạn cũng có thể đóng gói bằng dấu phẩy mà không viết ngoặc, chẳng hạn vi_tri = 2, 4. Tuy vậy, dùng ngoặc giúp các cặp tọa độ dễ đọc hơn trong ví dụ dài.
Set lưu mỗi giá trị khác nhau một lần
Lan ghi tên các bạn đã ghé bàn. Một bạn có thể ghé nhiều lần, nhưng Lan chỉ cần biết có những ai:
luot = ["An", "Binh", "An", "Chi"]
ban = set(luot)
print(len(ban))
print("An" in ban)3 True
Tập hợp (set) giữ mỗi giá trị khác nhau một lần. set(luot) tạo set từ list; len cho số tên khác nhau. in kiểm tra một tên có trong set hay không. Set không phải danh sách có thứ tự để lấy theo chỉ số. Không dựa vào thứ tự hiển thị của set.
Tạo set rỗng bằng set(). Cặp {} tạo dictionary rỗng, không phải set rỗng:
ban = set()
ban.add("An")
ban.add("An")
print(len(ban))1
add thêm một giá trị; thêm lại "An" không làm tăng số phần tử. Giá trị trong set phải dùng được để kiểm tra và lưu ổn định: số, chuỗi hoặc tuple chỉ gồm các giá trị phù hợp đều dùng được. List có thể thay đổi nên không thể là phần tử set.
ban = set()
ban.add([2, 4])Lỗi khi chạy, ở dòng 2
TypeError: unhashable type: 'list'
Python báo TypeError. Nếu cần lưu vị trí cố định, dùng tuple (2, 4) thay cho list [2, 4]. Kiểm tra thành viên trong set thường thuận tiện và nhanh hơn tìm tuần tự trong list, nhưng không dùng set khi cần giữ số lần xuất hiện.
Muốn hiển thị tên theo thứ tự chữ cái, bạn có thể tạo một danh sách mới từ set:
ban = {"Chi", "An", "Binh"}
ten_sap_xep = sorted(ban)
print(ten_sap_xep)['An', 'Binh', 'Chi']
sorted(ban) trả về một list, không biến ban thành list. Tách bước lưu tập và bước trình bày giúp bạn không phụ thuộc thứ tự hiển thị của set.
Hợp, giao và hiệu so sánh hai set
Hai nhóm đăng ký hai buổi đọc sách. Hợp (union) gồm những bạn có mặt ở ít nhất một nhóm:
sang = {"An", "Binh"}
chieu = {"Binh", "Chi"}
print(sorted(sang | chieu))['An', 'Binh', 'Chi']
| tạo set mới gồm các phần tử của cả hai set, không lặp "Binh". sorted tạo list có thứ tự để kết quả dễ đọc; nó không thay đổi hai set ban đầu.
update thêm các phần tử của set thứ hai trực tiếp vào set thứ nhất. Khác với |, thao tác này thay đổi set ban đầu:
sang = {"An", "Binh"}
chieu = {"Binh", "Chi"}
sang.update(chieu)
print(sorted(sang))
print(sorted(chieu))['An', 'Binh', 'Chi'] ['Binh', 'Chi']
Giao (intersection) chỉ giữ phần tử có trong cả hai set:
sang = {"An", "Binh"}
chieu = {"Binh", "Chi"}
print(sorted(sang & chieu))['Binh']
& lấy giao, nên chỉ còn "Binh". Hiệu (difference) lấy những phần tử của set bên trái không có trong set bên phải:
sang = {"An", "Binh"}
chieu = {"Binh", "Chi"}
print(sorted(sang - chieu))
print(sorted(chieu - sang))['An'] ['Chi']
Thứ tự hai vế quan trọng với -: buổi sáng chỉ có thêm "An", buổi chiều chỉ có thêm "Chi". Cả hợp, giao và hiệu ở đây đều tạo set mới; chúng không xóa tên trong dữ liệu gốc.
Dictionary nối mỗi khóa với một giá trị
Lan muốn tra nhóm theo tên mà không cần duyệt từng dòng danh sách. Một từ điển (dictionary, viết tắt dict) lưu các cặp khóa và giá trị:
nhom = {"An": "Do", "Binh": "Xanh"}
print(nhom["An"])Do
Trong "An": "Do", "An" là khóa (key), còn "Do" là giá trị (value). nhom["An"] tìm giá trị theo khóa. Khóa là duy nhất trong một dictionary. Giá trị của các khóa khác nhau có thể giống nhau.
Thêm hoặc sửa bằng phép gán. Khi khóa đã có, phép gán cập nhật giá trị của khóa ấy:
nhom = {"An": "Do", "Binh": "Xanh"}
nhom["Chi"] = "Do"
nhom["An"] = "Xanh"
print(nhom["Chi"])
print(nhom["An"])Do Xanh
Chi chưa có nên được thêm; An đã có nên nhóm được thay. Có thể xóa một khóa bằng del:
nhom = {"An": "Do", "Binh": "Xanh"}
del nhom["An"]
print("An" in nhom)False
Với dictionary, in kiểm tra khóa, không kiểm tra giá trị. Đọc một khóa thiếu bằng ngoặc vuông gây lỗi:
nhom = {"An": "Do"}
print(nhom["Chi"])Lỗi khi chạy, ở dòng 2
KeyError: 'Chi'
Python báo KeyError. Nếu thiếu khóa là trường hợp bình thường, dùng get với giá trị mặc định:
nhom = {"An": "Do"}
print(nhom.get("Chi", "Chua co"))
print(nhom.get("An", "Chua co"))Chua co Do
get trả giá trị mặc định khi khóa vắng mặt. Nó không tự thêm khóa vào dictionary. Nếu bỏ đối số mặc định, get trả None khi thiếu khóa.
Một giá trị bằng 0 vẫn có thể thuộc về một khóa đã tồn tại. Vì thế, đừng dùng chính giá trị 0 làm bằng chứng rằng khóa vắng mặt:
gia = {"A": 0}
print("A" in gia)
print("B" in gia)
print(gia.get("B", 0))True False 0
Mã A có trong bảng với giá 0. Mã B không có, dù get cũng trả 0 theo mặc định. Khi cần phân biệt hai trường hợp, hãy kiểm tra khóa bằng in hoặc chọn một giá trị mặc định phù hợp với bài toán.
Duyệt khóa, giá trị và từng cặp
Để xem tên và nhóm của từng bạn, dùng items() và mở gói từng cặp:
nhom = {"An": "Do", "Binh": "Xanh"}
for ten, mau in nhom.items():
print(ten, mau)An Do Binh Xanh
Mỗi lượt, items() cung cấp một cặp khóa và giá trị. ten, mau nhận lần lượt hai phần. Nếu chỉ cần một phía, dùng keys() hoặc values():
nhom = {"An": "Do", "Binh": "Xanh"}
for ten in nhom.keys():
print(ten)
for mau in nhom.values():
print(mau)An Binh Do Xanh
Các giá trị có thể trùng, nên values() vẫn cho từng giá trị tương ứng với mỗi khóa. Duyệt trực tiếp for ten in nhom cũng duyệt khóa. Không thêm hay xóa khóa trong khi đang duyệt dictionary; hãy xây dựng dữ liệu cần thiết ở bước riêng.
Tuple số có thể làm khóa để tra một vị trí trên lưới:
ban = {(0, 1): "Sach", (1, 2): "But"}
print(ban[(1, 2)])But
(1, 2) gộp hàng và cột thành một khóa. Tuple phải gồm các phần tử dùng được làm khóa; một tuple chứa list không phù hợp vì list có thể thay đổi.
Bạn có thể đổi list thành tuple bằng tuple(...) để dùng tọa độ số làm khóa. list(...) đổi ngược thành danh sách mới khi cần sửa:
toa_do = [1, 2]
khoa = tuple(toa_do)
ban = {khoa: "But"}
moi = list(khoa)
moi[0] = 3
print(ban[khoa])
print(khoa, moi)But (1, 2) [3, 2]
Đếm số lần và gom dữ liệu theo nhóm
Để đếm lượt ghé, mỗi tên là khóa và số lượt là giá trị. Bắt đầu số đếm của tên chưa gặp bằng 0:
luot = ["An", "Binh", "An", "Chi", "An"]
dem = {}
for ten in luot:
dem[ten] = dem.get(ten, 0) + 1
print(dem["An"])
print(dem["Binh"])3 1
Ở mỗi lượt, get(ten, 0) lấy số hiện có hoặc 0 nếu tên mới. Cộng 1 rồi gán lại cho cùng khóa. Khi gặp An lần thứ ba, số đếm tăng từ 2 lên 3.
| Lượt | Tên | Số lượt của An |
|---|---|---|
| 1 | An | 1 |
| 2 | Binh | 1 |
| 3 | An | 2 |
| 4 | Chi | 2 |
| 5 | An | 3 |
Nếu muốn giữ danh sách tên của từng nhóm thay vì đếm, giá trị của mỗi khóa có thể là một list:
ds = [("An", "Do"), ("Binh", "Xanh"), ("Chi", "Do")]
nhom = {}
for ten, mau in ds:
if mau not in nhom:
nhom[mau] = []
nhom[mau].append(ten)
print(nhom["Do"])['An', 'Chi']
Lần đầu gặp một màu, tạo list rỗng cho màu ấy. Sau đó thêm tên vào list tương ứng. Không gán [] ở mọi lượt, vì như vậy sẽ xóa những tên đã gom trước đó.
Bạn có thể kiểm tra nhóm chưa xuất hiện trước khi đọc danh sách của nhóm. Điều này tránh truy cập một khóa thiếu và giúp chương trình xử lý cả nhóm chưa có người:
nhom = {"Do": ["An", "Chi"]}
mau = "Xanh"
if mau in nhom:
print(len(nhom[mau]))
else:
print(0)0
Với dữ liệu này, nhánh else in 0. Nếu cần giữ thứ tự đăng ký, list phù hợp; nếu chỉ cần các tên khác nhau, bạn có thể dùng một set làm giá trị của từng nhóm. Hãy chọn cấu trúc theo thông tin bài toán cần giữ.
Sắp xếp các cặp theo giá trị
Sau khi đếm, Lan muốn xem tên theo số lượt tăng dần. items() cung cấp các cặp; sorted tạo list các cặp được sắp xếp:
dem = {"An": 3, "Binh": 1, "Chi": 3}
thu_tu = sorted(dem.items(), key=lambda cap: (cap[1], cap[0]))
for ten, so_luot in thu_tu:
print(ten, so_luot)Binh 1 An 3 Chi 3
Mỗi cap là tuple (tên, số lượt). cap[1] là tiêu chí chính; cap[0] phá hòa theo tên. Kết quả là list mới, còn dem vẫn là dictionary ban đầu. Để số lượt lớn đứng trước nhưng tên bằng lượt vẫn theo thứ tự tăng, đổi tiêu chí số thành -cap[1]:
dem = {"An": 3, "Binh": 1, "Chi": 3}
for ten, so_luot in sorted(dem.items(), key=lambda cap: (-cap[1], cap[0])):
print(ten, so_luot)An 3 Chi 3 Binh 1
Dấu trừ ở đây đổi chiều tiêu chí số. reverse=True đảo cả thứ tự tên khi số lượt bằng nhau, nên không đáp ứng quy tắc phá hòa vừa nêu.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
Viết (5) cho tuple một phần tử | Đây là số trong ngoặc | Viết (5,) |
Gán t[0] = 1 với tuple | Tuple không cho thay phần tử | Tạo tuple mới hoặc dùng list |
Dùng {} để tạo set rỗng | {} tạo dictionary | Dùng set() |
Tra d[k] khi khóa thiếu | Khóa không có gây KeyError | Kiểm tra k in d hoặc dùng get |
Dùng list làm khóa | List có thể thay đổi | Dùng tuple với các phần tử phù hợp |
Gán list rỗng ở mọi lượt gom | Các tên cũ bị thay mất | Chỉ tạo list khi khóa chưa có |
| Đổi hai vế của hiệu set | Hiệu phụ thuộc thứ tự | Đặt set cần giữ ở bên trái |
Tóm tắt
- Tuple giữ các phần tử có thứ tự, hỗ trợ đóng gói và mở gói; không gán thay một phần tử.
- Set giữ giá trị riêng biệt;
|,&,-lần lượt tạo hợp, giao, hiệu. - Dictionary ánh xạ khóa duy nhất tới giá trị; dùng
getkhi khóa có thể thiếu. items()cho các cặp, thuận tiện khi duyệt và sắp xếp theo giá trị.- Đếm bằng
get(khoa, 0) + 1; gom nhóm bằnglistgắn với từng khóa.