DHDataHọc
Lý thuyếtBài 17/305 phút đọc+10 XP

Set và tuple

Bạn sẽ học được

  • Set loại trùng lặp, kiểm tra thành viên nhanh và hỗ trợ hợp/giao/hiệu.
  • Vì sao set không giữ thứ tự và không chứa được list.
  • Cách khử trùng nhưng giữ thứ tự ban đầu.
  • Tuple là dãy bất biến, dùng làm khóa và trả nhiều giá trị.
  • Khi nào chọn set, tuple hay list.

Khái niệm cốt lõi

Set là tập hợp không trùng lặp, không có thứ tự. Tuple là dãy có thứ tự nhưng bất biến. Cả hai đều dùng ngoặc tròn/móc, nhưng mục đích khác hẳn list.

code
print(sorted(set([1, 2, 2, 3])))
code
[1, 2, 3]

set tự bỏ phần tử trùng. Vì không giữ thứ tự, đừng dựa vào thứ tự khi in — hãy sorted() nếu cần kết quả ổn định.

Phép toán tập hợp

code
a = {1, 2, 3}
b = {3, 4}
print(sorted(a | b), sorted(a & b), sorted(a - b))
code
[1, 2, 3, 4] [3] [1, 2]
PhépKý hiệuÝ nghĩa
Hợpa | bCó trong a hoặc b
Giaoa & bCó trong cả hai
Hiệua - bCó trong a, không trong b
Hiệu đối xứnga ^ bCó trong đúng một bên

Đây là công cụ rất tiện để so sánh danh sách: mã đơn hàng ngày hôm qua so với hôm nay, tập email đã đăng ký so với tập đã gửi.

code
yesterday = {"A1", "A2", "A3"}
today = {"A2", "A3", "A4"}
print(sorted(today - yesterday))   # đơn mới
print(sorted(yesterday & today))   # đơn còn
print(sorted(yesterday - today))   # đơn biến mất
code
['A4']
['A2', 'A3']
['A1']

Thêm, xoá và kiểm tra

code
a = {1, 2, 3}
a.add(5)
a.discard(99)
print(sorted(a))
code
[1, 2, 3, 5]
Thao tácViệc nó làmKhi phần tử không có
add(x)Thêm—
discard(x)Xoá nếu cóKhông lỗi
remove(x)XoáKeyError
pop()Lấy phần tử bất kỳKeyError nếu rỗng

discard an toàn hơn remove khi bạn không chắc phần tử tồn tại. Kiểm tra thành viên bằng in là O(1) trung bình — nhanh hơn nhiều so với list O(n):

code
ids = {"u1", "u2", "u3"}
print("u2" in ids, "u9" in ids)
code
True False

Vì vậy khi phải kiểm tra x in collection nhiều lần trên dữ liệu lớn, chuyển collection sang set trước là tối ưu đơn giản và hiệu quả.

Set chỉ chứa giá trị hashable

code
{[1, 2]}
code
TypeError: unhashable type: 'list'

List thay đổi được nên không băm ổn định; set cần băm để tra cứu nhanh. Muốn chứa nhiều giá trị bất biến, dùng tuple: {(1, 2), (3, 4)}. Tương tự, dict không dùng list làm khóa được.

Khử trùng nhưng giữ thứ tự

set phá thứ tự, nên list(set(ids)) không bảo toàn thứ tự gốc. Khi cần giữ lần xuất hiện đầu tiên, dùng dict — từ Python 3.7 dict giữ thứ tự chèn:

code
ids = [3, 1, 3, 2, 1]
print(list(dict.fromkeys(ids)))
code
[3, 1, 2]

dict.fromkeys(ids) tạo dict với các khóa theo thứ tự gặp đầu tiên, giá trị None; lấy list() khóa là xong. Đây là mẹo chuẩn để "unique có thứ tự" mà không cần thư viện ngoài.

Tuple — dãy bất biến

code
point = (10.5, 106.7)
x, y = point
print(x, y)
code
10.5 106.7

Tuple dùng được làm khóa dict vì bất biến. Nó cũng là cách trả nhiều giá trị từ hàm:

code
def min_max(nums):
    return min(nums), max(nums)

low, high = min_max([4, 9, 1])
print(low, high)
code
1 9

Thực ra hàm trả một tuple (1, 9); phép gán mở gói (unpacking) tách thành hai biến. Có thể dùng dấu * để gom phần còn lại: first, *rest = (1, 2, 3).

Tuple không sửa được:

code
point = (10.5, 106.7)
point[0] = 0
code
TypeError: 'tuple' object does not support item assignment

Nhưng nếu tuple chứa list bên trong, list đó vẫn sửa được — bất biến chỉ ở lớp ngoài.

Hoán đổi giá trị không cần biến tạm:

code
p, q = 1, 2
p, q = q, p
print(p, q)
code
2 1

Tuple một phần tử và namedtuple

code
print(type((1)), type((1,)))
code
<class 'int'> <class 'tuple'>

(1) chỉ là số 1 trong ngoặc; tuple một phần tử bắt buộc có dấu phẩy (1,). Đây là lỗi im lặng: code chạy nhưng giá trị không phải tuple.

Khi tuple có nhiều trường và bạn muốn truy cập theo tên, dùng namedtuple:

code
from collections import namedtuple

Point = namedtuple("Point", "x y")
p = Point(10.5, 106.7)
print(p, p.x)
code
Point(x=10.5, y=106.7) 10.5

Namedtuple vẫn là tuple — bất biến, nhẹ, unpack được — nhưng đọc hơn p[0]. Với cấu trúc phức tạp hơn, dataclass phù hợp hơn.

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

Nhu cầuChọn
Dãy có thứ tự, cần sửalist
Dãy cố định, làm khóa, trả nhiều giá trịtuple
Loại trùng, kiểm tra thành viên nhanhset
Ánh xạ khóa → giá trịdict
Khử trùng giữ thứ tựdict.fromkeys
Tập bất biến làm khóafrozenset

Thực hành

  1. Khử trùng một list số bằng set, in kết quả đã sorted.
  2. Tính hợp, giao, hiệu của hai tập mã đơn hàng.
  3. Dùng discard và remove với phần tử không tồn tại, so sánh lỗi.
  4. Khử trùng list ID nhưng giữ thứ tự bằng dict.fromkeys.
  5. Viết hàm trả (min, max) và unpack kết quả.
  6. Thử tạo tuple một phần tử không có dấu phẩy và kiểm tra type.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Thứ tự set thay đổiSet không giữ thứ tựsorted() khi cần ổn định
TypeError: unhashableChứa list/dictDùng tuple hoặc frozenset
Mất thứ tự sau khử trùnglist(set(...))list(dict.fromkeys(...))
KeyError khi removePhần tử không códiscard hoặc kiểm tra in
(1) không phải tupleThiếu dấu phẩyViết (1,)
Sửa tuple báo lỗiTuple bất biếnTạo tuple mới hoặc dùng list
in trên list chậmList tìm tuyến tínhChuyển sang set

Tóm tắt

  • Set khử trùng, kiểm tra in O(1), hỗ trợ | & - ^.
  • Set không giữ thứ tự và chỉ chứa giá trị hashable.
  • dict.fromkeys khử trùng mà giữ thứ tự.
  • Tuple bất biến: khóa dict, trả nhiều giá trị, unpack.
  • Tuple một phần tử cần dấu phẩy (x,).

Dùng ← → để chuyển bài