Bài 3.4. Python list: sắp xếp với sort

Chào mừng các bạn quay trở lại với series lập trình Python! Trong bài học này, chúng ta sẽ đi sâu vào một thao tác cực kỳ phổ biến và quan trọng khi làm việc với danh sách (list) trong Python: sắp xếp. Cụ thể, chúng ta sẽ tìm hiểu về phương thức sort() - một công cụ mạnh mẽ được tích hợp sẵn trong Python để giúp bạn tổ chức dữ liệu trong list một cách gọn gàng và hiệu quả.

Tại sao cần sắp xếp List?

Hãy tưởng tượng bạn có một danh sách điểm số của sinh viên, một danh sách tên khách hàng, hay một danh sách các sản phẩm theo giá. Việc sắp xếp chúng theo một thứ tự nhất định (ví dụ: điểm từ cao đến thấp, tên theo thứ tự alphabet, giá từ thấp đến cao) sẽ giúp bạn:

  • Dễ dàng tìm kiếm thông tin: Tìm sinh viên có điểm cao nhất, khách hàng có tên bắt đầu bằng 'A', hay sản phẩm rẻ nhất trở nên đơn giản hơn nhiều.
  • Phân tích dữ liệu hiệu quả: Việc dữ liệu được sắp xếp giúp dễ dàng nhận ra xu hướng, xác định các giá trị lớn nhất/nhỏ nhất.
  • Trình bày dữ liệu rõ ràng: Một danh sách được sắp xếp luôn dễ đọc và dễ hiểu hơn một danh sách lộn xộn.

Python cung cấp cho chúng ta phương thức sort() để thực hiện nhiệm vụ này một cách nhanh chóngtiện lợi.

Giới thiệu Phương thức sort()

Phương thức sort() là một phương thức (method) được gắn liền với đối tượng list. Điều này có nghĩa là bạn sẽ gọi nó trực tiếp từ một biến list đã tồn tại.

Điểm cốt lõi cần nhớ về sort():

  1. Sắp xếp tại chỗ (in-place): sort() thay đổi trực tiếp danh sách gốc mà nó được gọi. Nó không tạo ra một danh sách mới đã được sắp xếp.
  2. Trả về None:sort() sửa đổi danh sách gốc, nó không trả về danh sách đã sắp xếp. Thay vào đó, nó trả về None. Đây là một lỗi phổ biến mà người mới bắt đầu hay mắc phải!

    # Lỗi thường gặp!
    my_list = [3, 1, 4, 1, 5, 9, 2]
    sorted_list = my_list.sort() # Sai! sorted_list sẽ là None
    print(my_list)     # Output: [1, 1, 2, 3, 4, 5, 9] (my_list đã bị thay đổi)
    print(sorted_list) # Output: None
    

Cú pháp cơ bản:

ten_list.sort(key=None, reverse=False)
  • ten_list: Tên biến chứa danh sách bạn muốn sắp xếp.
  • key: (Tùy chọn) Một hàm được áp dụng cho mỗi phần tử trước khi so sánh. Chúng ta sẽ tìm hiểu kỹ hơn về tham số cực kỳ mạnh mẽ này sau.
  • reverse: (Tùy chọn) Một giá trị boolean. Nếu là True, danh sách sẽ được sắp xếp theo thứ tự giảm dần. Mặc định là False (tăng dần).

Sắp xếp cơ bản (Tăng dần)

Theo mặc định, sort() sẽ sắp xếp các phần tử theo thứ tự tăng dần.

Ví dụ 1: Sắp xếp danh sách số

numbers = [5, 1, 8, 0, 3, 9, 2]
print(f"Danh sách ban đầu: {numbers}")

numbers.sort() # Sắp xếp tại chỗ

print(f"Danh sách sau khi sort(): {numbers}")

Giải thích:

  • Chúng ta có danh sách numbers chứa các số nguyên lộn xộn.
  • Gọi numbers.sort(). Python sẽ so sánh các phần tử và sắp xếp lại chúng ngay trong chính numbers.
  • Kết quả in ra cho thấy numbers đã được sắp xếp từ nhỏ đến lớn.

Kết quả:

Danh sách ban đầu: [5, 1, 8, 0, 3, 9, 2]
Danh sách sau khi sort(): [0, 1, 2, 3, 5, 8, 9]

Ví dụ 2: Sắp xếp danh sách chuỗi

fruits = ["chuối", "táo", "nho", "cam", "ổi"]
print(f"Danh sách ban đầu: {fruits}")

fruits.sort() # Sắp xếp tại chỗ

print(f"Danh sách sau khi sort(): {fruits}")

Giải thích:

  • Đối với chuỗi, sort() sẽ sắp xếp theo thứ tự từ điển (alphabetical order).
  • fruits.sort() sắp xếp lại danh sách fruits theo thứ tự chữ cái.

Kết quả:

Danh sách ban đầu: ['chuối', 'táo', 'nho', 'cam', 'ổi']
Danh sách sau khi sort(): ['cam', 'chuối', 'nho', 'ổi', 'táo']

Lưu ý quan trọng: sort() chỉ hoạt động khi các phần tử trong danh sách có thể so sánh được với nhau. Bạn không thể sắp xếp một danh sách chứa cả số và chuỗi nếu không cung cấp một khóa tùy chỉnh (sẽ nói ở phần sau).

mixed_list = [5, "táo", 1, "chuối"]
# mixed_list.sort() # Dòng này sẽ gây ra lỗi TypeError!

Sắp xếp giảm dần với reverse=True

Để đảo ngược thứ tự sắp xếp (từ lớn đến nhỏ đối với số, hoặc Z-A đối với chuỗi), bạn chỉ cần truyền tham số reverse=True vào phương thức sort().

Ví dụ 3: Sắp xếp số giảm dần

scores = [75, 92, 88, 65, 92, 80]
print(f"Điểm ban đầu: {scores}")

scores.sort(reverse=True) # Thêm reverse=True

print(f"Điểm sau khi sort giảm dần: {scores}")

Giải thích:

  • Tham số reverse=True yêu cầu sort() sắp xếp danh sách theo thứ tự ngược lại so với mặc định.
  • Danh sách scores giờ đây được sắp xếp từ điểm cao nhất đến thấp nhất.

Kết quả:

Điểm ban đầu: [75, 92, 88, 65, 92, 80]
Điểm sau khi sort giảm dần: [92, 92, 88, 80, 75, 65]

Ví dụ 4: Sắp xếp chuỗi giảm dần

planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter"]
print(f"Hành tinh ban đầu: {planets}")

planets.sort(reverse=True) # Sắp xếp Z-A

print(f"Hành tinh sau khi sort giảm dần: {planets}")

Kết quả:

Hành tinh ban đầu: ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter']
Hành tinh sau khi sort giảm dần: ['Venus', 'Mercury', 'Mars', 'Jupiter', 'Earth']

Sắp xếp nâng cao với tham số key

Đây là lúc sort() thể hiện sự linh hoạtmạnh mẽ của nó! Tham số key cho phép bạn cung cấp một hàm. Hàm này sẽ được gọi cho từng phần tử trong danh sách trước khi thực hiện so sánh. Giá trị trả về của hàm key sẽ được dùng để quyết định thứ tự sắp xếp, thay vì sử dụng chính các phần tử gốc.

Hàm key phải là một hàm nhận một đối số (phần tử của danh sách) và trả về một giá trị có thể so sánh được.

Ví dụ 5: Sắp xếp chuỗi theo độ dài

Giả sử bạn muốn sắp xếp một danh sách các từ theo độ dài của chúng, từ ngắn nhất đến dài nhất.

words = ["Python", "is", "awesome", "and", "fun"]
print(f"Từ ngữ ban đầu: {words}")

# Sử dụng hàm len tích hợp sẵn làm key
words.sort(key=len)

print(f"Từ ngữ sắp xếp theo độ dài: {words}")

Giải thích:

  • Chúng ta truyền len (hàm tích hợp sẵn để lấy độ dài) làm giá trị cho tham số key.
  • Trước khi so sánh hai từ, sort() sẽ gọi hàm len() cho mỗi từ đó. Ví dụ, để so sánh "Python" và "is", sort() sẽ so sánh len("Python") (là 6) và len("is") (là 2).
  • Vì 2 < 6, nên "is" sẽ đứng trước "Python" trong danh sách đã sắp xếp.
  • Lưu ý rằng danh sách cuối cùng vẫn chứa các chuỗi gốc, nhưng thứ tự của chúng được quyết định bởi độ dài.

Kết quả:

Từ ngữ ban đầu: ['Python', 'is', 'awesome', 'and', 'fun']
Từ ngữ sắp xếp theo độ dài: ['is', 'and', 'fun', 'Python', 'awesome']

Ví dụ 6: Sắp xếp chuỗi không phân biệt chữ hoa/thường

Sắp xếp mặc định sẽ coi "Apple" đứng trước "banana" vì 'A' < 'b'. Nếu bạn muốn sắp xếp không phân biệt hoa thường, bạn có thể dùng key để chuyển tất cả về cùng một dạng (ví dụ: chữ thường) trước khi so sánh.

names = ["Alice", "bob", "Charlie", "david"]
print(f"Tên ban đầu: {names}")

# Sử dụng phương thức str.lower làm key
names.sort(key=str.lower)

print(f"Tên sắp xếp không phân biệt hoa/thường: {names}")

Giải thích:

  • str.lower là một phương thức của chuỗi, chuyển chuỗi thành chữ thường. Khi dùng làm key, sort() sẽ gọi item.lower() cho mỗi item trong names.
  • Ví dụ: "Alice" sẽ được so sánh dựa trên "alice", "bob" dựa trên "bob", "Charlie" dựa trên "charlie".
  • Kết quả là danh sách được sắp xếp theo thứ tự alphabet, bỏ qua sự khác biệt về chữ hoa/thường ban đầu. Các phần tử gốc vẫn giữ nguyên dạng chữ hoa/thường của chúng.

Kết quả:

Tên ban đầu: ['Alice', 'bob', 'Charlie', 'david']
Tên sắp xếp không phân biệt hoa/thường: ['Alice', 'bob', 'Charlie', 'david']

(Trong ví dụ này, thứ tự không đổi vì chúng đã gần đúng thứ tự, nhưng nếu có "Zebra" và "apple", "apple" sẽ đứng trước "Zebra" sau khi sắp xếp)

Ví dụ 7: Sắp xếp danh sách các tuple theo phần tử thứ hai

Bạn có thể có một danh sách các tuple, ví dụ: (tên, tuổi), và muốn sắp xếp theo tuổi.

people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
print(f"Danh sách người ban đầu: {people}")

# Sử dụng hàm lambda làm key để lấy phần tử thứ hai (index 1)
people.sort(key=lambda person: person[1])

print(f"Danh sách sắp xếp theo tuổi (tăng dần): {people}")

Giải thích:

  • Ở đây, chúng ta dùng lambda để tạo một hàm ẩn danh ngắn gọn. lambda person: person[1] định nghĩa một hàm nhận một đối số person (là một tuple) và trả về phần tử ở index 1 của tuple đó (là tuổi).
  • sort() sử dụng giá trị tuổi này để sắp xếp các tuple.

Kết quả:

Danh sách người ban đầu: [('Alice', 30), ('Bob', 25), ('Charlie', 35)]
Danh sách sắp xếp theo tuổi (tăng dần): [('Bob', 25), ('Alice', 30), ('Charlie', 35)]

Ví dụ 8: Sắp xếp danh sách các dictionary theo một giá trị cụ thể (giảm dần)

products = [
    {'name': 'Laptop', 'price': 1200},
    {'name': 'Mouse', 'price': 25},
    {'name': 'Keyboard', 'price': 75},
    {'name': 'Monitor', 'price': 300}
]
print(f"Sản phẩm ban đầu:\n{products}")

# Sắp xếp theo giá (price) giảm dần
products.sort(key=lambda product: product['price'], reverse=True)

print(f"\nSản phẩm sắp xếp theo giá (giảm dần):\n{products}")

Giải thích:

  • key=lambda product: product['price'] lấy giá trị của khóa 'price' trong mỗi dictionary làm tiêu chí so sánh.
  • reverse=True đảm bảo thứ tự sắp xếp là từ giá cao nhất đến thấp nhất.

Kết quả:

Sản phẩm ban đầu:
[{'name': 'Laptop', 'price': 1200}, {'name': 'Mouse', 'price': 25}, {'name': 'Keyboard', 'price': 75}, {'name': 'Monitor', 'price': 300}]

Sản phẩm sắp xếp theo giá (giảm dần):
[{'name': 'Laptop', 'price': 1200}, {'name': 'Monitor', 'price': 300}, {'name': 'Keyboard', 'price': 75}, {'name': 'Mouse', 'price': 25}]

Tính ổn định của sort() (Sort Stability)

Một đặc tính quan trọng của sort() trong Python là nó ổn định (stable). Điều này có nghĩa là nếu hai phần tử có cùng khóa sắp xếp (ví dụ: hai từ có cùng độ dài, hoặc hai người có cùng tuổi), thứ tự tương đối của chúng trong danh sách gốc sẽ được bảo toàn trong danh sách đã sắp xếp.

Ví dụ 9: Minh họa tính ổn định

data = [
    ('apple', 5),
    ('banana', 6),
    ('cherry', 6), # Cùng độ dài với banana
    ('date', 4)
]
print(f"Dữ liệu ban đầu: {data}")

# Sắp xếp theo độ dài của tên (phần tử thứ nhất)
data.sort(key=lambda item: len(item[0]))

print(f"Dữ liệu sắp xếp theo độ dài tên: {data}")

Giải thích:

  • Chúng ta sắp xếp dựa trên độ dài của chuỗi ở vị trí đầu tiên trong mỗi tuple.
  • "banana" và "cherry" đều có độ dài là 6. Trong danh sách gốc, 'banana' đứng trước 'cherry'.
  • sort() ổn định, nên trong kết quả cuối cùng, 'banana' vẫn sẽ đứng trước 'cherry'.

Kết quả:

Dữ liệu ban đầu: [('apple', 5), ('banana', 6), ('cherry', 6), ('date', 4)]
Dữ liệu sắp xếp theo độ dài tên: [('date', 4), ('apple', 5), ('banana', 6), ('cherry', 6)]

(Lưu ý: 'date' (4) < 'apple' (5) < 'banana' (6) == 'cherry' (6). 'banana' vẫn đứng trước 'cherry')

Phân biệt sort() và hàm sorted()

Python còn có một hàm tích hợp sẵn khác là sorted(). Điều quan trọng là phải phân biệt rõ ràng giữa list.sort()sorted():

  1. list.sort():

    • Là một phương thức của đối tượng list.
    • Sắp xếp danh sách tại chỗ (thay đổi danh sách gốc).
    • Trả về None.
  2. sorted(iterable):

    • Là một hàm tích hợp sẵn.
    • Nhận vào một đối tượng có thể lặp lại (iterable) bất kỳ (list, tuple, string, set, dictionary keys, v.v.).
    • Trả về một danh sách mới đã được sắp xếp.
    • Không thay đổi đối tượng gốc.

Ví dụ so sánh:

my_list = [3, 1, 4, 1, 5]
print(f"Original my_list: {my_list}")

# Sử dụng sort()
my_list.sort()
print(f"my_list after sort(): {my_list}") # my_list đã thay đổi

# Sử dụng sorted()
another_list = [8, 6, 7, 5, 9]
print(f"\nOriginal another_list: {another_list}")
new_sorted_list = sorted(another_list)
print(f"new_sorted_list from sorted(): {new_sorted_list}") # Danh sách mới được tạo
print(f"another_list after sorted(): {another_list}")   # another_list không đổi

Kết quả:

Original my_list: [3, 1, 4, 1, 5]
my_list after sort(): [1, 1, 3, 4, 5]

Original another_list: [8, 6, 7, 5, 9]
new_sorted_list from sorted(): [5, 6, 7, 8, 9]
another_list after sorted(): [8, 6, 7, 5, 9]

Việc lựa chọn giữa sort()sorted() phụ thuộc vào nhu cầu của bạn:

  • Nếu bạn muốn sắp xếp danh sách hiện tại và không cần giữ lại bản gốc, sort() hiệu quả hơn vì nó không cần tạo danh sách mới.
  • Nếu bạn cần giữ lại danh sách gốc hoặc muốn sắp xếp một iterable không phải là list (và nhận kết quả là list), hãy dùng sorted().

Phương thức sort() là một công cụ không thể thiếu khi làm việc với list trong Python. Khả năng sắp xếp tại chỗ, cùng với sự linh hoạt của tham số key, giúp bạn dễ dàng tổ chức dữ liệu theo hầu hết mọi tiêu chí bạn có thể nghĩ ra. Hãy thực hành với các ví dụ trên và thử nghiệm với dữ liệu của riêng bạn để nắm vững kỹ năng này!

Comments

There are no comments at the moment.