⭐ Vision Engineer · Chặng 1Bài 4/11Bài học~75 phút

Python cơ bản: list, tuple, dict và set

Lưu và thống kê dữ liệu sản xuất: slicing, list comprehension, dict cấu hình, đếm lỗi Pareto, set phát hiện mã trùng.

Python cơ bản: list, tuple, dict và set

Học xong bài này, bạn sẽ

  • Chọn cấu trúc dữ liệu phù hợp
  • Duyệt, lọc, sắp xếp dữ liệu
  • Dùng list comprehension và dict để thống kê

Nên học trước: Rẽ nhánh và vòng lặp

Nội dung bài viết
  1. 1. list: danh sách có thứ tự, thay đổi được
  2. 2. List comprehension: lọc và biến đổi trong một dòng
  3. 3. tuple: dãy không thay đổi được
  4. 4. dict: tra cứu theo tên
  5. 5. set: tập hợp không trùng lặp
  6. 6. Chọn cấu trúc nào?
  7. Tóm tắt
  8. Bài tập

Một ca sản xuất sinh ra hàng nghìn kết quả, mỗi kết quả gồm nhiều thông tin: mã sản phẩm, kết quả, kích thước đo, thời gian. Bài này dạy bốn cấu trúc dữ liệu có sẵn của Python để lưu và xử lý những tập dữ liệu như vậy: list, tuple, dict và set.

1. list: danh sách có thứ tự, thay đổi được

duong_kinh = [12.48, 12.51, 12.47, 12.62, 12.50]

print(duong_kinh[0])      # phần tử đầu tiên: 12.48 (chỉ số bắt đầu từ 0)
print(duong_kinh[-1])     # phần tử cuối: 12.5
print(len(duong_kinh))    # số phần tử: 5

duong_kinh.append(12.49)          # thêm vào cuối
duong_kinh[3] = 12.52             # sửa phần tử thứ 4
print(duong_kinh)
12.48
12.5
5
[12.48, 12.51, 12.47, 12.52, 12.5, 12.49]

Cắt list (slicing)

Cú pháp [bat_dau:ket_thuc:buoc], không gồm vị trí kết thúc, giống range(). Đây cũng chính là cú pháp cắt vùng ảnh (ROI) bằng NumPy mà bạn sẽ dùng hằng ngày:

so = [0, 10, 20, 30, 40, 50, 60]
print(so[1:4])    # [10, 20, 30]
print(so[:3])     # 3 phần tử đầu: [0, 10, 20]
print(so[-2:])    # 2 phần tử cuối: [50, 60]
print(so[::2])    # cách một lấy một: [0, 20, 40, 60]

Thống kê nhanh

do = [12.48, 12.51, 12.47, 12.52, 12.50, 12.49]
print("Nhỏ nhất:", min(do), "Lớn nhất:", max(do))
print(f"Trung bình: {sum(do) / len(do):.3f}")
print("Sắp xếp:", sorted(do))
Nhỏ nhất: 12.47 Lớn nhất: 12.52
Trung bình: 12.495
Sắp xếp: [12.47, 12.48, 12.49, 12.5, 12.51, 12.52]

sorted() tạo list mới đã sắp xếp; còn do.sort() sắp xếp ngay trên list cũ.

2. List comprehension: lọc và biến đổi trong một dòng

Thay vì viết vòng lặp 4 dòng, Python cho phép tạo list mới trong một dòng:

do = [12.48, 12.51, 12.47, 12.62, 12.50, 12.38]

ngoai_dung_sai = [d for d in do if not 12.40 <= d <= 12.60]
print(ngoai_dung_sai)                 # [12.62, 12.38]

do_um = [round(d / 25.4, 4) for d in do]   # đổi mm sang inch
print(do_um[:2])                      # [0.4913, 0.4925]

Đọc là: "lấy d với mỗi d trong do, nếu điều kiện đúng". Đừng lạm dụng: nếu logic dài hơn một dòng, hãy viết vòng lặp for cho dễ đọc.

3. tuple: dãy không thay đổi được

Tuple viết bằng ngoặc tròn. Dùng cho dữ liệu cố định đi thành cặp, thành bộ, ví dụ tọa độ hay kích thước ảnh:

tam = (320, 240)                  # tọa độ (x, y)
kich_thuoc = (1080, 1920, 3)      # (cao, rộng, số kênh màu) — đúng thứ tự NumPy dùng

x, y = tam                        # "mở gói" tuple thành từng biến
cao, rong, kenh = kich_thuoc
print(f"Tâm ({x}, {y}), ảnh {rong}x{cao}, {kenh} kênh")
Tâm (320, 240), ảnh 1920x1080, 3 kênh

Tuple không sửa được (tam[0] = 5 sẽ báo lỗi TypeError). Chính vì thế nó an toàn để làm khóa của dict. OpenCV và NumPy trả về rất nhiều tuple, ví dụ img.shape.

4. dict: tra cứu theo tên

Dict lưu cặp khóa → giá trị. Thay vì nhớ "phần tử thứ 3 là đường kính", bạn gọi đúng tên:

san_pham = {
    "ma": "SP-0042",
    "ket_qua": "NG",
    "duong_kinh": 12.62,
    "ly_do": "ngoài dung sai",
}

print(san_pham["ma"], san_pham["ket_qua"])
san_pham["anh"] = "ST3_000042_NG.png"         # thêm khóa mới
print(san_pham.get("nhiet_do", "không có"))   # get() trả giá trị mặc định nếu thiếu khóa

for khoa, gia_tri in san_pham.items():
    print(f"  {khoa}: {gia_tri}")
SP-0042 NG
không có
  ma: SP-0042
  ket_qua: NG
  duong_kinh: 12.62
  ly_do: ngoài dung sai
  anh: ST3_000042_NG.png

Truy cập khóa không tồn tại bằng san_pham["nhiet_do"] sẽ gây KeyError. Dùng .get() khi không chắc khóa có tồn tại.

Dict làm cấu hình

Tham số của thuật toán nên gom vào một dict để dễ chỉnh, dễ lưu ra file (bài 6 và chặng OpenCV sẽ dùng cách này):

cau_hinh = {"nguong_sang": 120, "dien_tich_min": 500, "roi": (100, 80, 400, 300)}
print(cau_hinh["roi"])

Đếm theo nhóm

ly_do_ng = ["xước", "lệch tâm", "xước", "ba via", "xước", "lệch tâm"]

dem = {}
for ld in ly_do_ng:
    dem[ld] = dem.get(ld, 0) + 1
print(dem)

from collections import Counter          # cách ngắn hơn có sẵn trong Python
print(Counter(ly_do_ng).most_common(2))
{'xước': 3, 'lệch tâm': 2, 'ba via': 1}
[('xước', 3), ('lệch tâm', 2)]

Bảng "lỗi nào xảy ra nhiều nhất" (biểu đồ Pareto) là báo cáo mà quản lý chất lượng hỏi đầu tiên.

5. set: tập hợp không trùng lặp

ma_da_doc = ["A01", "A02", "A01", "A03", "A02"]
print(set(ma_da_doc))               # {'A01', 'A02', 'A03'} (thứ tự có thể khác)
print(len(set(ma_da_doc)) != len(ma_da_doc))   # True: có mã bị đọc trùng

ke_hoach = {"A01", "A02", "A03", "A04"}
thuc_te = {"A01", "A02", "A03"}
print("Thiếu:", ke_hoach - thuc_te)   # {'A04'}

Set rất nhanh khi kiểm tra "đã có hay chưa" ("A01" in thuc_te), kể cả với hàng triệu phần tử. Ứng dụng: phát hiện mã DataMatrix bị in trùng ở chặng 7.

6. Chọn cấu trúc nào?

CầnDùng
Dãy có thứ tự, thêm/sửa/xóa đượclist
Bộ giá trị cố định (tọa độ, kích thước)tuple
Tra cứu theo tên, cấu hình, một bản ghidict
Loại trùng, kiểm tra tồn tại nhanhset

Trong thực tế bạn sẽ kết hợp: list các dict là cách tự nhiên để lưu kết quả cả ca, mỗi dict là một sản phẩm.

ca_sang = [
    {"ma": "SP-01", "ket_qua": "OK", "dk": 12.50},
    {"ma": "SP-02", "ket_qua": "NG", "dk": 12.63},
    {"ma": "SP-03", "ket_qua": "OK", "dk": 12.49},
]
ng = [sp["ma"] for sp in ca_sang if sp["ket_qua"] == "NG"]
dk_ok = [sp["dk"] for sp in ca_sang if sp["ket_qua"] == "OK"]
print("Mã NG:", ng)
print(f"Đường kính TB hàng OK: {sum(dk_ok) / len(dk_ok):.3f}")
Mã NG: ['SP-02']
Đường kính TB hàng OK: 12.495
Bẫy: gán list là dùng chungViết b = a với a là list thì b và a là cùng một list: sửa b thì a cũng đổi. Muốn bản sao độc lập, dùng b = a.copy(). Bẫy này cũng xảy ra với ảnh NumPy (bài 9).

Tóm tắt

  • list: [ ], chỉ số từ 0, slicing [a:b] không gồm b, append, sorted.
  • List comprehension: [f(x) for x in ds if dieu_kien].
  • tuple: ( ), cố định, mở gói x, y = tam.
  • dict: {khóa: giá trị}, .get(), .items(); dùng cho cấu hình và bản ghi.
  • set: loại trùng, kiểm tra tồn tại nhanh, phép trừ tập hợp.

Bài tập

Bài 1. Thống kê đo lường

Cho 10 giá trị đo: [25.01, 24.98, 25.03, 25.07, 24.99, 25.00, 24.93, 25.02, 25.04, 24.97], dung sai 25,00 ± 0,05. Tính trung bình, số lượng NG và tỉ lệ NG (%).

Xem đáp án
do = [25.01, 24.98, 25.03, 25.07, 24.99, 25.00, 24.93, 25.02, 25.04, 24.97]
ng = [d for d in do if not 24.95 <= d <= 25.05]
print(f"Trung bình: {sum(do) / len(do):.3f}")
print("NG:", ng)
print(f"Tỉ lệ NG: {len(ng) / len(do):.0%}")
Trung bình: 25.004
NG: [25.07, 24.93]
Tỉ lệ NG: 20%

Bài 2. Pareto lỗi

Từ danh sách kết quả dạng dict dưới đây, in bảng số lượng theo từng lý do NG, sắp xếp giảm dần.

ket_qua = [
    {"ma": 1, "kq": "NG", "ly_do": "xước"}, {"ma": 2, "kq": "OK", "ly_do": ""},
    {"ma": 3, "kq": "NG", "ly_do": "thiếu chi tiết"}, {"ma": 4, "kq": "NG", "ly_do": "xước"},
    {"ma": 5, "kq": "NG", "ly_do": "bẩn"}, {"ma": 6, "kq": "NG", "ly_do": "xước"},
]
Xem đáp án
from collections import Counter
dem = Counter(r["ly_do"] for r in ket_qua if r["kq"] == "NG")
for ly_do, so in dem.most_common():
    print(f"{ly_do:15s} {so}")
xước            3
thiếu chi tiết  1
bẩn             1

Bài 3. Mã bị đọc trùng

Cho danh sách mã DataMatrix đọc được trong ca: ["M100","M101","M102","M101","M103","M100"]. In ra các mã bị đọc nhiều hơn một lần.

Xem đáp án
ma = ["M100", "M101", "M102", "M101", "M103", "M100"]
da_thay, trung = set(), set()
for m in ma:
    if m in da_thay:
        trung.add(m)
    da_thay.add(m)
print(sorted(trung))
['M100', 'M101']
Bài tiếp theo
Ban biên tập HọcFree

Chúng tôi viết những bài hướng dẫn dễ hiểu, thực tế để ai cũng có thể tự học lập trình và ngoại ngữ, hoàn toàn miễn phí.