Câu 1Cái nào dưới đây tạo đúng một set?
Cách sử dụng set trong Python
Học cách dùng kiểu set trong Python: tập hợp không trùng lặp, không thứ tự và các phép toán tập hợp cơ bản.
set là gì — không trùng lặp, không có thứ tự
set (set) cũng giống list và tuple là kiểu để gom nhiều giá trị lại. Nhưng nó có hai quy tắc khiến nó khác biệt hẳn.
- Mỗi giá trị chỉ xuất hiện một lần (các giá trị trùng lặp tự động bị loại bỏ)
- Thứ tự không được giữ lại (vị trí không có ý nghĩa)
Dù bạn có đưa cùng một giá trị vào hai lần, trong set cuối cùng vẫn chỉ còn một bản.
(Ở ví dụ này, a cuối cùng chỉ còn một.)
Khai báo bằng cách bọc các giá trị trong { và }, cách nhau bởi dấu phẩy.
Để tạo một set rỗng, dùng set() chứ không phải {}. Viết {} sẽ cho bạn một dict rỗng.
# Các giá trị trùng lặp tự động bị loại bỏ
set_a = {"a", "b", "c", "d", "a"}
print(set_a) # {'a', 'b', 'c', 'd'} (thứ tự phụ thuộc môi trường)
print(type(set_a)) # <class 'set'>
print(len(set_a)) # 4 (chữ 'a' trùng lặp gộp lại thành một)
# Trộn số và các kiểu khác đều được
mixed = {1, "apple", 3.14}
# Dùng set() cho set rỗng
empty_set = set()
empty_dict = {} # cái này trở thành "dict rỗng"
print(type(empty_set)) # <class 'set'>
print(type(empty_dict)) # <class 'dict'>
# Dùng in / not in để kiểm tra phần tử
print("a" in set_a) # True
print("z" not in set_a) # True
Không có thứ tự, nên không truy cập bằng index
Vì set không có thứ tự, bạn không thể truy cập bằng index như set_a[0] (sẽ bị TypeError). Nếu cần phần tử thứ N, hãy chuyển sang list hoặc tuple trước rồi lấy từ đó. Chọn theo nhu cầu: list / tuple khi cần thứ tự, set khi không cần.
Thêm và xóa phần tử
set là kiểu mutable, nên bạn có thể thêm và xóa phần tử sau khi khai báo.
Dưới đây là năm phương thức bạn sẽ dùng nhiều nhất.
| Phương thức | Vai trò | Ghi chú |
|---|---|---|
| add(x) | Thêm phần tử x | Không làm gì nếu đã có sẵn (bỏ qua trùng lặp) |
| remove(x) | Xóa phần tử x | KeyError nếu x không có |
| discard(x) | Xóa phần tử x | Không báo lỗi dù x không có |
| pop() | Xóa và trả về một phần tử bất kỳ | Không xác định trước được chọn cái nào |
| clear() | Xóa toàn bộ phần tử | Để lại một set rỗng |
fruits = {"apple", "banana", "lemon"}
# add
fruits.add("grape")
fruits.add("apple") # đã có, bị bỏ qua
print(fruits) # {'apple', 'banana', 'lemon', 'grape'}
# remove (báo lỗi nếu không có)
fruits.remove("banana")
# fruits.remove("melon") # KeyError
# discard không báo lỗi dù không có
fruits.discard("melon") # không xảy ra gì
print(fruits) # {'apple', 'lemon', 'grape'}
# Xóa và trả về một phần tử bất kỳ
picked = fruits.pop()
print(picked) # một phần tử nào đó (mỗi lần chạy một khác)
# Xóa toàn bộ
fruits.clear()
print(fruits) # set()
Dùng remove khi bạn muốn biết nếu giá trị không có ở đó (nó sẽ báo lỗi).
Dùng discard khi bạn chỉ muốn xóa nếu có và không quan tâm trường hợp còn lại.
Phép toán tập hợp — union, intersection, difference, symmetric difference
Điểm mạnh nhất của set là phép toán tập hợp. Bạn có thể kết hợp hai set để lấy ra phần tử chung, hoặc phần tử chỉ có ở một bên, chỉ trong một dòng. Có thể làm điều tương tự bằng vòng for trên list, nhưng set nhanh hơn rất nhiều.
- Union — mọi phần tử xuất hiện ở một trong hai set
- Intersection — chỉ phần tử xuất hiện ở cả hai
- Difference — phần tử có trong
snhưng không có trongt - Symmetric difference — phần tử xuất hiện ở đúng một trong hai
Sơ đồ bên dưới là kết quả khi s = {a, b, c, d} và t = {c, d, e, f}.
Toán tử (| & - ^) và phương thức (union, ...) là tương đương — dùng cái nào cũng được.
s = {"a", "b", "c", "d"}
t = {"c", "d", "e", "f"}
# Union
print(s | t) # {'a', 'b', 'c', 'd', 'e', 'f'}
print(s.union(t)) # giống hệt
# Intersection
print(s & t) # {'c', 'd'}
print(s.intersection(t)) # giống hệt
# Difference (trong s, không có trong t)
print(s - t) # {'a', 'b'}
print(s.difference(t)) # giống hệt
# Symmetric difference (chỉ ở đúng một bên)
print(s ^ t) # {'a', 'b', 'e', 'f'}
print(s.symmetric_difference(t)) # giống hệt
Kiểm tra tập con (lưu ý bên lề)
a <= b hoặc a.issubset(b) cho bạn biết mọi phần tử của a có nằm trong b không, trả về True / False. Chiều ngược lại là a >= b hoặc a.issuperset(b).
Cái này dùng cho các kiểm tra như «tập quyền của user có chứa đủ các quyền yêu cầu không?». Ban đầu cứ tập trung vào bốn phép toán tập hợp rồi dùng những cái này khi thực sự cần.
Các pattern thực tế — khử trùng lặp và kiểm tra phần tử nhanh
Cuối cùng, hãy nắm vững hai pattern quen thuộc xuất hiện liên tục trong công việc thực tế.
list(set(...)) — «chuyển thành set để loại trùng, rồi chuyển ngược lại thành list» — là idiom phổ biến.
Một dòng, nhanh, và sạch hơn hẳn vòng for với kiểm tra đã thấy chưa.
# Pattern ①: khử trùng lặp
numbers = [1, 2, 2, 2, 3, 4, 4, 5, 6, 6]
unique_numbers = list(set(numbers))
print(unique_numbers) # [1, 2, 3, 4, 5, 6] (thứ tự có thể khác)
# Pattern ②: kiểm tra phần tử nhanh
# x in set nhanh hơn x in list rất nhiều
allowed_users = {"alice", "bob", "carol"}
print("alice" in allowed_users) # True
print("dan" in allowed_users) # False
Tại sao in nhanh trên set
set được hậu thuẫn bởi bảng băm (hash table). Nó tra giá trị trực tiếp qua hash, nên việc tra cứu tốn thời gian gần như không đổi bất kể kích thước.
in trên list so sánh từng phần tử từ đầu, nên càng lớn càng chậm. Nếu bạn sẽ kiểm tra phần tử nhiều lần, hãy chuyển sang set trước để đạt tốc độ cao vượt trội.
Trong bài này, bạn đã thấy đặc điểm của set (không trùng lặp, không có thứ tự), các phương thức thêm/xóa, bốn phép toán tập hợp, và hai pattern thực tế — khử trùng lặp và kiểm tra phần tử nhanh.
«Mình không quan tâm thứ tự, chỉ cần bản duy nhất» / «Mình kiểm tra phần tử liên tục» / «Mình cần phần chung của hai tập» — nếu trường hợp nào hợp, là lúc dùng set.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Cho s = {1, 2, 3}, khi bạn chạy s.discard(99) thì điều gì xảy ra? (99 không có trong set)
Câu 3Với s = {1, 2, 3, 4} và t = {3, 4, 5, 6}, kết quả của s & t là gì?