Set là một cấu trúc rất hữu ích: lưu phần tử duy nhất, hỗ trợ kiểm tra membership O(1) trung bình, phù hợp khi ta quan tâm “có hay không” chứ không phải “ở vị trí nào”. Tuy nhiên vì set là unordered, ta không thể “sắp xếp trực tiếp” trên set như list. Khi cần thứ tự — để in theo thứ tự, để tìm top-k hay để dùng binary search — ta cần chuyển sang một dữ liệu có thứ tự (list/tuple) hoặc dùng một cấu trúc dữ liệu đặc biệt “sorted set”.
Trong bài này hãy cùng mình tim hiểu :
- Bản chất việc sắp xếp set.
- Các cách phổ biến:
sorted()
, chuyển đổi thành list/tuple, duy trì bộ dữ liệu đã sắp bằngbisect
hoặc dùng thư viện ngoài. - Ví dụ chi tiết kèm giải thích từng bước.
- Phân tích độ phức tạp, lưu ý và các sai lầm thường gặp.
- Khi nào dùng cách nào.
1. Tổng quan về Set và Sorting
- Set trong Python: tập hợp các phần tử không trùng nhau, không có thứ tự (implementation uses hash tables).
- Immutable vs mutable:
set
là mutable (có thể thêm/xóa),frozenset
là immutable nhưng cũng unordered. - Sorting ở đây không phải thay đổi set, mà là tạo ra một biểu diễn có thứ tự từ set (ví dụ một list đã sắp).
- Hệ quả: mọi thao tác “sắp” đòi hỏi sao chép dữ liệu (O(n) memory) vì bản thân set không giữ thứ tự.
Kết luận ngắn: nếu mục tiêu chỉ là lấy dữ liệu theo thứ tự tạm thời thì sorted()
thường đủ. Nếu cần duy trì thứ tự khi chèn/xóa thì phải dùng cấu trúc khác.
2. Sắp xếp Set bằng hàm dựng sẵn (sorted()
)
Cách đơn giản nhất là dùng sorted()
— nó nhận bất kỳ iterable nào và trả về một list đã sắp.
my_set = {7, 2, 5, 1, 4}
# sorted returns a new list (ascending by default)
sorted_list = sorted(my_set) # [1, 2, 4, 5, 7]
# If you want a tuple result
sorted_tuple = tuple(sorted(my_set)) # (1, 2, 4, 5, 7)
# Sort descending
desc_list = sorted(my_set, reverse=True) # [7, 5, 4, 2, 1]
Giải thích chi tiết:
sorted()
sẽ iterate qua set, thu thập phần tử rồi sắp xếp (thường dùng Timsort) — time O(n log n), space O(n) (kết quả là list).- Vì set không có chỉ số,
sorted()
không thay đổi set gốc — nó tạo ra list mới. - Nếu cần giữ cấu trúc là set (nhưng muốn biểu diễn sắp), bạn có thể lưu list hoặc tuple sắp được; đặt lại vào set sẽ mất thứ tự.
Lưu ý: tuple(sorted(my_set))
tạo tuple bất biến chứa phần tử đã sắp.
3. Sắp xếp theo tiêu chí tùy chỉnh (key & reverse)
sorted()
hỗ trợ key
và reverse
. Điều này rất quan trọng khi set chứa phần tử phức tạp như tuple, dict keys, strings với yêu cầu đặc biệt.
people = {("Alice", 30), ("Bob", 25), ("Charlie", 30)}
# Sort by age ascending, then by name ascending (stable sort keeps order of equal keys)
sorted_by_age_then_name = sorted(people, key=lambda x: (x[1], x[0]))
# [('Bob', 25), ('Alice', 30), ('Charlie', 30)]
# Example: sort strings by length
words = {"apple", "fig", "banana", "kiwi"}
sorted_by_len = sorted(words, key=len) # ['fig', 'kiwi', 'apple', 'banana']
Giải thích: key
trả về giá trị so sánh cho mỗi phần tử; với tuple trả về tuple key để quyết định nhiều tiêu chí.
4. Khi cần “dữ liệu luôn sắp” — các lựa chọn duy trì sorted set
Nếu bạn cần dữ liệu luôn ở trạng thái sắp khi chèn/xóa (ví dụ leaderboard hay xử lý streaming), sorted(my_set)
mỗi lần sẽ tốn O(n log n) — không hiệu quả nếu thường xuyên cập nhật. Các lựa chọn:
4.1 Dùng list + bisect
(standard library)
Giữ một danh sách đã sắp và dùng bisect.insort
để chèn phần tử mới ở vị trí đúng — chèn O(n) (do dịch chuyển), tìm vị trí O(log n).
import bisect
# maintain a sorted list representing a set (unique items)
sorted_list = []
def insert_unique(sorted_list, value):
# find insertion point
pos = bisect.bisect_left(sorted_list, value)
# if value not present, insert
if pos == len(sorted_list) or sorted_list[pos] != value:
bisect.insort_left(sorted_list, value)
# Usage
insert_unique(sorted_list, 5)
insert_unique(sorted_list, 3)
insert_unique(sorted_list, 7)
print(sorted_list) # [3, 5, 7]
Giải thích: bisect
nhanh để tìm vị trí nhưng chèn vẫn O(n) vì phải dịch chuyển phần tử. Tuy nhiên nếu kích thước vừa phải và chèn tương đối ít, đây là cách đơn giản, không cần thư viện ngoài.
4.2 Dùng heap (heapq) — phù hợp top-k / priority
heapq
không giữ toàn bộ danh sách theo thứ tự nhưng giúp lấy min/max hiệu quả. Không thay thế cho sorted set nếu cần truy cập theo thứ tự toàn bộ.
4.3 Dùng thư viện bên thứ ba (ví dụ sortedcontainers.SortedSet
)
Có các thư viện như sortedcontainers
(nếu cài thêm) cung cấp SortedList
/SortedSet
với chèn/xóa O(log n) hoặc amortized tốt. Ví dụ cài pip install sortedcontainers
và dùng from sortedcontainers import SortedSet
.
Lưu ý: đây là thư viện ngoài, phù hợp khi hiệu năng quan trọng.
5. Các ví dụ thực tế
5.1 Lấy top-k lớn nhất từ set (efficient)
Nếu chỉ cần top-k, không cần sắp toàn bộ set — dùng heapq.nlargest
(O(n log k)):
import heapq
data_set = {15, 3, 9, 20, 7, 30}
top_3 = heapq.nlargest(3, data_set) # [30, 20, 15]
Giải thích: so với sorted(data_set, reverse=True)[:3]
là O(n log n), nlargest
tối ưu khi k nhỏ.
5.2 Lưu set theo thứ tự chèn nhưng sắp theo giá trị khi in (keep original)
Nếu bạn muốn giữ set gốc (vì tính unique & mutation) nhưng khi in ra cần sắp:
events = {"login", "logout", "signup", "purchase"}
# keep original set unchanged
print("Sorted events:", sorted(events))
# original set remains
Giải thích: đơn giản và an toàn — dùng sorted()
mỗi khi cần view.
5.3 Duy trì leaderboard (score set) bằng SortedList (concept)
Nếu bạn cần leaderboard luôn sắp xếp khi cập nhật, dùng list+bisect hoặc SortedSet từ thư viện.
import bisect
leaderboard = [] # sorted descending by score, store tuples (-score, user)
def add_score(leaderboard, user, score):
entry = (-score, user) # negative for descending order
pos = bisect.bisect_left(leaderboard, entry)
if pos == len(leaderboard) or leaderboard[pos] != entry:
leaderboard.insert(pos, entry) # O(n) insert
add_score(leaderboard, "alice", 120)
add_score(leaderboard, "bob", 150)
add_score(leaderboard, "charlie", 130)
# convert back to readable format
readable = [(user, -score) for score, user in leaderboard]
Giải thích: đây là minh họa đơn giản; nếu cần hiệu năng lớn, dùng specialized sorted data structure.
6. Độ phức tạp (time & space) — so sánh nhanh
Operation / Approach | Time complexity | Space complexity |
---|---|---|
sorted(my_set) | O(n log n) | O(n) |
tuple(sorted(my_set)) | O(n log n) | O(n) |
bisect.insort (list) | find O(log n), insert O(n) | O(n) |
heapq.nlargest(k, set) | O(n log k) | O(k) |
SortedSet (third-party) | typically O(log n) inserts | O(n) |
Giải thích:
sorted()
là lựa chọn đơn giản, chi phí O(n log n) cho mỗi lần sắp toàn bộ.- Nếu chỉ cần top-k thì
heapq.nlargest
hiệu quả hơn. - Duy trì thứ tự khi cập nhật thường yêu cầu cấu trúc chuyên dụng để tránh O(n) chèn.
7. Những sai lầm thường gặp khi sắp xếp Set (và cách tránh)
- Cố gắng gọi
.sort()
trên set →AttributeError
.
Fix: dùngsorted()
hoặc chuyển thành list. - Gán kết quả
sorted()
trở lại thành set → mất thứ tự.my_set = {3,1,2} my_set = set(sorted(my_set)) # WRONG: set() will lose order
Fix: lưu vào list/tuple nếu cần giữ thứ tự:sorted_list = sorted(my_set)
. - Quên rằng converting costs O(n) — nhiều lần chuyển đổi trong vòng lặp gây overhead lớn.
Fix: nếu cần thao tác nhiều lần, chuyển 1 lần và reuse sorted list, hoặc dùng data structure duy trì sắp thẳng. - Không quan tâm stability khi sort keys bằng nhau — với set phần tử duy nhất, nhưng nếu key bằng nhau (ví dụ sort theo length của strings), stable sort giữ thứ tự nhưng vì set unordered nên thứ tự gốc không xác định.
Giải thích: nếu muốn ổn định dựa trên thứ tự chèn, phải sử dụng cấu trúc giữ thứ tự chèn (e.g.list
hoặcdict
trong Python 3.7+). - Dùng thuật toán O(n²) (ví dụ chèn bằng list insert ở đầu) cho dữ liệu lớn → chậm.
Fix: chọn giải pháp O(n log n) hoặc data structure phù hợp.
8. Kết luận
- Set là unordered → không thể sắp trực tiếp; “sắp set” = tạo biểu diễn có thứ tự (list/tuple) từ set.
sorted()
là công cụ mạnh mẽ và đúng đắn cho hầu hết nhu cầu: nhanh, ổn định và dễ dùng. Tuy nhiên nó trả về list (sao chép dữ liệu) — ảnh hưởng bộ nhớ nếu dataset lớn.- Nếu cần top-k, dùng
heapq.nlargest
để tiết kiệm thời gian. - Nếu cần duy trì thứ tự khi cập nhật thường xuyên, dùng
bisect
+ list (đơn giản) hoặc thư viện chuyên dụng nhưsortedcontainers.SortedSet
(nếu performance quan trọng). - Tránh các sai lầm: gọi
.sort()
trên set, gán lại vào set làm mất order, chuyển đổi nhiều lần không cần thiết.
Mình khuyên: với các tác vụ thông thường, dùng sorted()
; khi yêu cầu hiệu năng ở mức cao hoặc cập nhật liên tục, đầu tư vào cấu trúc dữ liệu phù hợp.
9. Tài liệu tham khảo
- Python Software Foundation. Built-in Functions — sorted(). Python Documentation.
- Python Software Foundation. bisect — Array bisection algorithm. Python Standard Library docs.
- Python Software Foundation. heapq — Heap queue algorithm. Python Standard Library docs.
- (Optional)
sortedcontainers
project — a high-performance pure-Python sorted collections library.