Set là một trong những cấu trúc dữ liệu rất hữu ích trong Python: nó lưu trữ các phần tử không trùng lặp và không có thứ tự. Nhờ đó set thường dùng để loại bỏ bản sao, kiểm tra thuộc tính (membership) rất nhanh, và thực hiện các phép toán tập hợp (hợp, giao, hiệu) một cách trực tiếp.
Trong bài này mình sẽ đi từ cơ bản đến nâng cao: cách tạo set, thêm/xóa phần tử, các phép toán tập hợp (với cả phương thức và toán tử), các phương thức in-place, hiệu năng/độ phức tạp, frozenset (phiên bản bất biến), những lưu ý hay lỗi thường gặp…
Bảng cheat-sheet — các phương thức và toán tử quan trọng
Phương thức / Toán tử | Chức năng | Trả về / In-place | Complexity (avg) |
---|---|---|---|
s.add(x) | Thêm phần tử x | In-place | O(1) |
s.update(iter) | Thêm nhiều phần tử | In-place | O(k) |
s.remove(x) | Xóa x, KeyError nếu không có | In-place (exception) | O(1) |
s.discard(x) | Xóa x nếu có, không lỗi | In-place | O(1) |
s.pop() | Xóa và trả về 1 phần tử bất kỳ | In-place | O(1) |
s.clear() | Xóa toàn bộ | In-place | O(1) |
A.union(B) / `A | B` | Hợp | New set |
A.intersection(B) / A & B | Giao | New set | O(min(len(A),len(B))) |
A.difference(B) / A - B | Hiệu | New set | O(len(A)) |
A.symmetric_difference(B) / A ^ B | Hiệu đối xứng | New set | O(len(A)+len(B)) |
A.update(B) / `A | = B` | Hợp in-place | In-place |
A.intersection_update(B) / A &= B | Giao in-place | In-place | O(min(len(A),len(B))) |
s.copy() | Sao chép shallow | New set | O(len(s)) |
frozenset(iter) | Set bất biến | New frozenset | O(n) |
x in s | Membership test | Bool | O(1) |
1. Tạo Set (khởi tạo)
- Cách cơ bản: dùng
{}
với phần tử hoặc hàmset()
cho tập rỗng. - Quan trọng:
{}
tạo dict rỗng, do đó để tạo set rỗng phải dùngset()
.
# Create set from literal
numbers = {1, 2, 3, 4, 5}
# Create an empty set (note: {} is empty dict)
empty_set = set()
# Create set from iterable (list, tuple, generator)
items_from_list = set([1, 2, 2, 3]) # duplicates removed
print(items_from_list) # Output: {1, 2, 3}
# Set comprehension (concise & expressive)
squares = {x*x for x in range(6)} # {0, 1, 4, 9, 16, 25}
Giải thích: khi chuyển từ iterable -> set, các phần tử trùng sẽ bị loại bỏ. Set comprehension tương tự list comprehension nhưng trả về set.
2. Yêu cầu về phần tử: hashable vs unhashable
- Các phần tử của set phải là hashable (có
__hash__
hợp lệ): số, chuỗi, tuple (nếu các phần tử trong tuple cũng hashable). - Không thể thêm list hoặc dict vì chúng mutable và không hashable.
# Allowed
s = { (1, 2), "hello", 42 }
# Not allowed -> TypeError
# bad = {[1,2], 3} # list is unhashable
Giải thích: set dựa trên hash table nội bộ, nên mọi phần tử cần có giá băm cố định trong suốt vòng đời trong set.
3. Thêm và mở rộng set
add(x)
thêm một phần tử (nếu chưa tồn tại).update(iterable)
thêm nhiều phần tử từ iterable (list/tuple/set/generator).
fruits = {"apple", "banana"}
fruits.add("orange") # add single item
fruits.update(["mango", "grape"]) # add multiple
Lưu ý về hiệu năng: cả add
và update
trung bình là O(1) cho mỗi phần tử (do hash lookup/insert). Với update
, tổng chi phí ~ O(k) với k là số phần tử thêm.
4. Xóa phần tử và xử lý lỗi
remove(x)
— xóa phần tửx
, nếu không tồn tại sẽ raiseKeyError
.discard(x)
— xóa nếu tồn tại, không raise lỗi nếu không tồn tại.pop()
— xóa và trả về một phần tử ngẫu nhiên (vì set không có thứ tự).clear()
— xóa toàn bộ set.
items = {1, 2, 3}
items.discard(4) # safe even if 4 not present
try:
items.remove(4) # will raise KeyError
except KeyError:
print("4 is not in set")
removed = items.pop() # removes an arbitrary element
Best practice: nếu không chắc phần tử có tồn tại hay không, dùng discard()
để tránh try/except không cần thiết.
5. Phép toán tập hợp: method vs operator; in-place variants
Python hỗ trợ cả phương thức và toán tử cho các phép toán tập hợp. Quan trọng là phân biệt các thao tác trả về set mới vs thao tác biến đổi trực tiếp set hiện tại.
- Trả về set mới:
A.union(B)
hoặcA | B
A.intersection(B)
hoặcA & B
A.difference(B)
hoặcA - B
A.symmetric_difference(B)
hoặcA ^ B
- Biến đổi (in-place) set hiện tại:
A.update(B)
tương đươngA |= B
A.intersection_update(B)
tương đươngA &= B
A.difference_update(B)
tương đươngA -= B
A.symmetric_difference_update(B)
tương đươngA ^= B
A = {1,2,3}
B = {3,4,5}
print(A | B) # {1,2,3,4,5}
print(A & B) # {3}
print(A - B) # {1,2}
print(A ^ B) # {1,2,4,5}
# In-place example
A |= {6,7} # A is now {1,2,3,6,7}
Giải thích: dùng phương thức trả về mới khi cần giữ nguyên bộ ban đầu; dùng in-place khi muốn cập nhật và tránh tạo bản sao (tiết kiệm bộ nhớ).
6. Kiểm tra quan hệ tập hợp
issubset
/<=
: A là tập con của Bissuperset
/>=
: A là tập cha của Bisdisjoint
: không có phần tử chung
X = {1,2}
Y = {1,2,3}
print(X.issubset(Y)) # True
print(Y.issuperset(X))# True
print(X.isdisjoint({4,5})) # True
7. Duyệt (iterate) và unordered property
- Set là iterable, có thể dùng
for
để duyệt. - Không dùng index (không support
s[0]
) vì set không có thứ tự. - Lưu ý: thứ tự duyệt có thể khác nhau giữa các lần chạy hoặc phiên Python khác nhau.
colors = {"red", "green", "blue"}
for color in colors:
print(color) # order is arbitrary
Pitfall: không dựa vào thứ tự khi thiết kế logic; nếu cần thứ tự, dùng list
hoặc sorted()
.
8. Chuyển đổi giữa set và các kiểu khác; sao chép
set()
↔list()
/tuple()
để chuyển đổi.copy()
tạo một shallow copy. Nếu set chứa tuple (immutable), shallow copy đủ; nếu chứa tham chiếu đến cấu trúc mutable trong tuple thì cần chú ý.
lst = [1,2,2,3]
uniq = set(lst) # deduplicate
back_to_list = list(uniq) # convert back to list
s = {1, (2,3)}
s_copy = s.copy()
Ghi nhớ: s.copy()
là shallow copy; các phần tử immutable thì an toàn.
9. frozenset
— set bất biến
frozenset
là phiên bản immutable của set. Có thể làm key cho dict hoặc phần tử của set khác (vì nó hashable).- Tạo:
fs = frozenset([1,2,3])
- Không có
add
/remove
vì bất biến.
fs = frozenset([1,2,3])
my_dict = {fs: "value"} # allowed
Khi dùng: dùng frozenset khi cần khóa cố định hoặc constant set.
10. Best practices & pitfalls
- Dùng
discard
thay vìremove
nếu không chắc phần tử tồn tại. - Không dựa vào thứ tự của set; nếu cần order, dùng
sorted()
hoặclist
+ kỹ thuật seen. - Sử dụng
frozenset
khi cần set làm key dictionary hoặc làm phần tử của set khác. - Khi làm việc với dữ liệu lớn, cân nhắc bộ nhớ: set có overhead (hash table). Tuy nhiên nếu bạn cần membership test nhanh hay loại bỏ trùng, set thường đáng tiền.
- Tránh đưa các phần tử unhashable (list, dict) vào set — sẽ gây
TypeError
.
11. Kết luận (phiên bản chuyên nghiệp, súc tích)
Set là công cụ cực kỳ hiệu quả khi ta cần loại bỏ phần tử trùng lặp, kiểm tra thuộc tính nhanh hay thực hiện các phép toán tập hợp. Vì underlying là hash table, set cung cấp membership test và thao tác thêm/xóa với chi phí trung bình rất thấp (O(1)), nên thường là lựa chọn hàng đầu cho các bài toán cần truy vấn nhiều.
Tuy nhiên set cũng có hạn chế: không có thứ tự và chỉ chứa phần tử hashable. Vì vậy trong thiết kế hệ thống ta cần cân nhắc: nếu cần thứ tự hoặc phép truy cập theo index, nên dùng list/tuple; nếu cần key có thể dùng dict; nếu cần key immutable (ví dụ làm hằng số cấu hình), dùng frozenset
.
Nắm vững các phương thức (và in-place variants) cùng hiểu rõ độ phức tạp, các lỗi phổ biến (ví dụ remove
vs discard
), và biết cách kết hợp set với các kỹ thuật khác (set comprehension, trình tự giữ thứ tự khi dedupe) sẽ giúp bạn viết code Python chính xác, hiệu quả và chuyên nghiệp hơn.
12. Tài liệu tham khảo
- Python Software Foundation. The Python Standard Library: Built-in Types – Set. Python Official Documentation. Truy cập tại: https://docs.python.org/3/library/stdtypes.html#set
- Real Python. Sets in Python: How to Use Them Effectively. Real Python Tutorial. Truy cập tại: https://realpython.com/python-sets/
- W3Schools. Python Sets. W3Schools Online Tutorials. Truy cập tại: https://www.w3schools.com/python/python_sets.asp
- GeeksforGeeks. Python Set – Methods and Operations. GeeksforGeeks Programming Resource. Truy cập tại: https://www.geeksforgeeks.org/python-set-methods/
- Luciano Ramalho. Fluent Python (2nd Edition). O’Reilly Media, 2022. — Chương 3: Dữ liệu tập hợp và ánh xạ (Sets and Mappings).