Học bằng cách đọc theo thứ tự

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)

Đặc điểm của set — trùng lặp biến mất, thứ tự không cố định
{a,b,c,d,a}thành set{a,b,c,d}(không thứ tự)khai báokết quả

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.

Hãy kiểm tra cách set hoạt động.

① Tạo tags = {"python", "web", "python", "ai", "web"}, rồi print() nội dung và len(tags). Bạn sẽ thấy trùng lặp bị gộp lại và số lượng giảm.

print() cả "python" in tags"go" in tags để kiểm tra phần tử bằng True / False.

(Nếu code chạy đúng, phần giải thích sẽ hiện ra.)

Python Editor

Chạy code để xem đầu ra

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ứcVai tròGhi chú
add(x)Thêm phần tử xKhông làm gì nếu đã có sẵn (bỏ qua trùng lặp)
remove(x)Xóa phần tử xKeyError nếu x không có
discard(x)Xóa phần tử xKhô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()
Khi nào dùng remove, khi nào dùng discard
remove(x)x không cóKeyErrordiscard(x)x không cókhông xảyra gìxóalỗixóaOK

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.

Hãy quản lý tag sở thích của một user.

interests = {"python", "web"} đã được chuẩn bị.

① Dùng add để thêm "ai".

② Thử add lại "python" đã có, và xác nhận set không thay đổi.

discard "go" (tag không có) và xác nhận không bị báo lỗi.

print() interests cuối cùng.

Python Editor

Chạy code để xem đầu ra

Phép toán tập hợp — union, intersection, difference, symmetric difference

Điểm mạnh nhất của setphé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 s nhưng không có trong t
  • 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}.

Bốn phép toán tập hợp
Unions | t.union{a,b,c,d,e,f}Intersections & t.intersection{c, d}Differences - t.difference{a, b}Symmetric diffs ^ t.symmetric_difference{a,b,e,f}

Toán tử (| & - ^)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.

Từ danh sách đăng ký của hai lớp, hãy tìm sinh viên đăng ký cả hai và sinh viên đăng ký chỉ một.

math = {"Minh", "Linh", "An", "Thảo"}english = {"An", "Thảo", "Hải", "Tuấn"} đã được chuẩn bị.

print() sinh viên đăng ký cả hai lớp (intersection).

print() sinh viên đăng ký chỉ một lớp (symmetric difference).

Python Editor

Chạy code để xem đầu ra

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ế.

Khử trùng lặp list bằng set
[1,2,2,3,4,4,5]list(set(...))[1,2,3,4,5]chuyển đổikết quả

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.

Hãy khử trùng lặp các bản ghi log và kiểm tra nhanh xem một user cụ thể có xuất hiện không.

logins = ["alice", "bob", "alice", "carol", "bob", "alice"] đã được chuẩn bị.

① Dùng set để khử trùng lặp vào unique_logins (dạng list), rồi print() nó.

② Tạo login_set bằng cách chuyển thẳng unique_logins thành set, và dùng in để print() xem "carol""dan" có là phần tử không.

Python Editor

Chạy code để xem đầu ra

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.

QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Cái nào dưới đây tạo đúng một set?

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}t = {3, 4, 5, 6}, kết quả của s & t là gì?