Bài 6.1 - Hàm sort có sẵn của Python

Chào mừng các bạn đến với bài viết sâu hơn về một trong những thao tác cực kỳ phổ biếnquan trọng trong lập trình: sắp xếp dữ liệu. Dù bạn đang làm việc với danh sách các con số, tên, hay các đối tượng phức tạp hơn, việc đưa chúng vào một trật tự nhất định là điều thường xuyên phải làm.

May mắn thay, Python cung cấp cho chúng ta hai công cụ mạnh mẽcó sẵn để giải quyết bài toán sắp xếp một cách hiệu quả:

  1. Phương thức list.sort(): Dành riêng cho các đối tượng danh sách (list).
  2. Hàm sorted(): Hoạt động trên mọi loại đối tượng iterable (có thể lặp qua được) và trả về một danh sách mới đã được sắp xếp.

Chúng ta hãy cùng đi sâu vào cách sử dụng từng công cụ này nhé!

1. Phương thức list.sort()

Phương thức list.sort() là một phương thức của đối tượng list. Điều này có nghĩa là bạn chỉ có thể gọi nó trên một danh sách.

Điểm quan trọng nhất cần ghi nhớ về list.sort() là nó sẽ sắp xếp danh sách ngay tại chỗ (in-place). Điều này có nghĩa là nó thay đổi trực tiếp danh sách ban đầu và không tạo ra danh sách mới.

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

Mặc định, list.sort() sẽ sắp xếp các phần tử theo thứ tự tăng dần (ascending) đối với số và theo thứ tự từ điển (alphabetical) đối với chuỗi.

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

# Danh sách gốc
my_numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("Danh sách gốc:", my_numbers)

# Gọi phương thức sort()
my_numbers.sort()

# In danh sách sau khi sắp xếp (đã bị thay đổi)
print("Danh sách sau khi sort():", my_numbers)

Giải thích code:

  • Ban đầu, chúng ta có danh sách my_numbers theo thứ tự ngẫu nhiên.
  • Khi gọi my_numbers.sort(), Python sẽ sắp xếp các phần tử bên trong my_numbers theo thứ tự tăng dần.
  • Lưu ý rằng chúng ta không gán kết quả của my_numbers.sort() cho biến nào cả, bởi vì nó thay đổi danh sách gốc trực tiếp. Dòng print("Danh sách sau khi sort():", my_numbers) in ra chính danh sách my_numbers sau khi đã được sắp xếp.

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

# Danh sách gốc
my_words = ["banana", "apple", "cherry", "date"]
print("Danh sách gốc:", my_words)

# Gọi phương thức sort()
my_words.sort()

# In danh sách sau khi sắp xếp
print("Danh sách sau khi sort():", my_words)

Giải thích code:

  • Tương tự như số, list.sort() sắp xếp các chuỗi dựa trên thứ tự từ điển (alphabetical).
  • "apple" đứng trước "banana", "banana" đứng trước "cherry", v.v. Danh sách my_words được thay đổi thành ['apple', 'banana', 'cherry', 'date'].
Sắp xếp giảm dần với reverse=True

Để sắp xếp theo thứ tự giảm dần (descending), bạn chỉ cần thêm tham số reverse=True khi gọi phương thức sort().

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

my_numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("Danh sách gốc:", my_numbers)

# Sắp xếp giảm dần
my_numbers.sort(reverse=True)

print("Danh sách sau khi sort(reverse=True):", my_numbers)

Giải thích code:

  • Việc thêm reverse=True vào lệnh gọi sort() đảo ngược thứ tự sắp xếp mặc định.
  • Danh sách my_numbers giờ đây được sắp xếp từ số lớn nhất đến số nhỏ nhất: [9, 6, 5, 4, 3, 2, 1, 1].
Sắp xếp theo tiêu chí tùy chỉnh với key

Tham số key là một tính năng rất mạnh mẽ cho phép bạn xác định một tiêu chí hoặc một hàm để sắp xếp. Thay vì so sánh trực tiếp các phần tử, Python sẽ gọi hàm được cung cấp trong key cho mỗi phần tử, và sau đó sử dụng giá trị trả về của hàm đó để so sánh và sắp xếp.

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

my_words = ["banana", "apple", "cherry", "date"]
print("Danh sách gốc:", my_words)

# Sắp xếp theo độ dài của chuỗi
my_words.sort(key=len)

print("Danh sách sau khi sort(key=len):", my_words)

# Sắp xếp theo độ dài giảm dần
my_words.sort(key=len, reverse=True)
print("Danh sách sau khi sort(key=len, reverse=True):", my_words)

Giải thích code:

  • Ở đây, chúng ta sử dụng hàm len (hàm trả về độ dài của một đối tượng) làm giá trị cho tham số key.
  • Thay vì sắp xếp theo thứ tự từ điển ("apple", "banana"), Python sẽ sắp xếp dựa trên kết quả của len() cho mỗi từ. Độ dài của các từ là: "banana" (6), "apple" (5), "cherry" (6), "date" (4).
  • Khi sắp xếp tăng dần theo độ dài, kết quả là ['date', 'apple', 'banana', 'cherry']. Lưu ý rằng "banana" và "cherry" có cùng độ dài (6), thứ tự tương đối giữa chúng trong danh sách gốc được giữ nguyên (gọi là sắp xếp ổn định - stable sort).
  • Kết hợp key=len với reverse=True sẽ sắp xếp theo độ dài giảm dần.

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

Thường chúng ta có danh sách các bản ghi (có thể là tuple hoặc dictionary), và muốn sắp xếp chúng dựa trên một trường cụ thể.

students = [('Alice', 'A', 15), ('Bob', 'B', 12), ('Charlie', 'B', 16)]
print("Danh sách gốc:", students)

# Sắp xếp theo tên (phần tử thứ 0 trong mỗi tuple)
students.sort(key=lambda student: student[0])
print("Sắp xếp theo tên:", students)

# Sắp xếp theo tuổi (phần tử thứ 2 trong mỗi tuple)
students.sort(key=lambda student: student[2])
print("Sắp xếp theo tuổi:", students)

# Sắp xếp theo điểm (phần tử thứ 1), nếu điểm bằng nhau thì sắp xếp theo tuổi (phần tử thứ 2)
students.sort(key=lambda student: (student[1], student[2]))
print("Sắp xếp theo điểm và tuổi:", students)

Giải thích code:

  • Chúng ta sử dụng hàm lambda (một loại hàm ẩn danh nhỏ) để tạo ra hàm tức thời cho tham số key.
  • lambda student: student[0] nghĩa là "đối với mỗi student (là một tuple), hãy trả về phần tử ở chỉ số 0". Python sẽ sử dụng giá trị này (tên) để sắp xếp.
  • lambda student: student[2] sử dụng phần tử ở chỉ số 2 (tuổi) làm khóa sắp xếp.
  • lambda student: (student[1], student[2]) là một kỹ thuật rất hữu ích. Khi key trả về một tuple, Python sẽ so sánh các tuple đó từng phần tử một từ trái sang phải. Đầu tiên, nó so sánh phần tử ở chỉ số 1 (điểm). Nếu hai sinh viên có cùng điểm ('B'), nó sẽ chuyển sang so sánh phần tử ở chỉ số 2 (tuổi) để phá vỡ sự đồng hạng. Kết quả là các sinh viên được sắp xếp trước hết theo điểm, sau đó theo tuổi trong nhóm cùng điểm.
Lưu ý: list.sort() trả về None

Một lỗi phổ biến mà người mới học hay mắc phải là mong đợi list.sort() trả về danh sách đã sắp xếp. Nó không làm vậy! Nó trả về giá trị đặc biệt None.

my_list = [5, 2, 8]
sorted_list_var = my_list.sort() # SAI LẦM!
print(sorted_list_var)
print(my_list) # Danh sách gốc đã bị thay đổi, nhưng biến mới là None

Giải thích code:

  • Dòng my_list.sort() thực hiện việc sắp xếp trên my_list thành [2, 5, 8].
  • Tuy nhiên, giá trị mà my_list.sort() trả vềNone.
  • Do đó, biến sorted_list_var nhận giá trị là None, chứ không phải danh sách đã sắp xếp. Hãy luôn gọi list.sort() trên một dòng riêng biệt và sử dụng lại biến danh sách ban đầu sau đó.

2. Hàm sorted()

Khác với list.sort() chỉ dành cho danh sách, sorted() là một hàm có sẵn của Python (built-in function) và nó hoạt động trên mọi loại đối tượng iterable.

Điểm mấu chốt của sorted() là nó luôn luôn trả về một danh sách mới đã được sắp xếp. Đối tượng iterable ban đầu không bị thay đổi.

Cú pháp cơ bản: sorted(iterable, *, key=None, reverse=False)

Sắp xếp các loại iterable khác nhau

Ví dụ 6: Sắp xếp Tuple

my_tuple = (3, 1, 4, 1, 5, 9)
print("Tuple gốc:", my_tuple)

# Sử dụng sorted()
sorted_list_from_tuple = sorted(my_tuple)

print("Danh sách mới từ tuple:", sorted_list_from_tuple)
print("Tuple gốc sau khi sorted():", my_tuple) # Tuple gốc không đổi

Giải thích code:

  • Hàm sorted() nhận my_tuple làm đối số.
  • Nó xử lý các phần tử trong tuple và trả về một danh sách mới [1, 1, 3, 4, 5, 9] đã được sắp xếp.
  • Tuple gốc my_tuple vẫn giữ nguyên giá trị ban đầu vì tuple là đối tượng bất biến (immutable).

Ví dụ 7: Sắp xếp Chuỗi

my_string = "python"
print("Chuỗi gốc:", my_string)

# Sử dụng sorted()
sorted_list_from_string = sorted(my_string)

print("Danh sách các ký tự đã sắp xếp:", sorted_list_from_string)
print("Chuỗi gốc sau khi sorted():", my_string) # Chuỗi gốc không đổi

Giải thích code:

  • Khi áp dụng sorted() cho một chuỗi, nó coi chuỗi như một chuỗi các ký tự lặp lại được.
  • Hàm trả về một danh sách mới chứa các ký tự của chuỗi được sắp xếp theo thứ tự từ điển: ['h', 'n', 'o', 'p', 't', 'y']. Chuỗi gốc "python" không thay đổi.

Ví dụ 8: Sắp xếp các Khóa trong Dictionary

my_dict = {'apple': 3, 'banana': 1, 'cherry': 2}
print("Từ điển gốc:", my_dict)

# Sử dụng sorted()
sorted_keys = sorted(my_dict)

print("Danh sách các khóa đã sắp xếp:", sorted_keys)
print("Từ điển gốc sau khi sorted():", my_dict)

Giải thích code:

  • Khi gọi sorted() trên một dictionary, mặc định nó sẽ hoạt động trên các khóa của dictionary.
  • Hàm trả về một danh sách mới chứa các khóa của từ điển được sắp xếp theo thứ tự từ điển: ['apple', 'banana', 'cherry']. Từ điển gốc không bị thay đổi thứ tự bên trong (mặc dù từ Python 3.7, dictionary giữ thứ tự chèn, nhưng sorted() vẫn trả về danh sách khóa).
Tham số reversekey trong sorted()

Hàm sorted() cũng chấp nhận các tham số reversekey hoạt động tương tự như với phương thức list.sort().

Ví dụ 9: Sử dụng reverse=True với sorted()

my_numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("Iterable gốc:", my_numbers)

# Sắp xếp giảm dần với sorted()
sorted_desc = sorted(my_numbers, reverse=True)

print("Danh sách giảm dần mới:", sorted_desc)
print("Iterable gốc sau khi sorted():", my_numbers)

Giải thích code:

  • Tham số reverse=True làm cho sorted() trả về một danh sách được sắp xếp theo thứ tự giảm dần.
  • Kết quả là [9, 6, 5, 4, 3, 2, 1, 1], nhưng danh sách my_numbers ban đầu vẫn là [3, 1, 4, 1, 5, 9, 2, 6].

Ví dụ 10: Sử dụng key với sorted()

Các ví dụ về key với list.sort() (sắp xếp chuỗi theo độ dài, sắp xếp tuple theo phần tử) cũng hoạt động y hệt với sorted(), chỉ khác là sorted() sẽ trả về một danh sách mới thay vì thay đổi tại chỗ.

my_words = ["banana", "apple", "cherry", "date"]
print("Iterable gốc:", my_words)

# Sắp xếp theo độ dài của chuỗi với sorted()
sorted_by_len = sorted(my_words, key=len)

print("Danh sách mới sắp xếp theo độ dài:", sorted_by_len)
print("Iterable gốc sau khi sorted():", my_words)

students = [('Alice', 'A', 15), ('Bob', 'B', 12), ('Charlie', 'B', 16)]
print("\nIterable gốc:", students)

# Sắp xếp theo tuổi với sorted()
sorted_by_age = sorted(students, key=lambda student: student[2])
print("Danh sách mới sắp xếp theo tuổi:", sorted_by_age)
print("Iterable gốc sau khi sorted():", students)

Giải thích code:

  • Hàm sorted() sử dụng tham số key tương tự như list.sort() để xác định tiêu chí sắp xếp tùy chỉnh.
  • Sự khác biệt chính là nó luôn trả về một danh sách mới. Danh sách my_wordsstudents gốc không hề bị thay đổi thứ tự.

3. Khi nào sử dụng list.sort() và khi nào sử dụng sorted()?

Việc lựa chọn giữa hai công cụ này phụ thuộc vào nhu cầu của bạn:

  • Sử dụng list.sort() khi:
    • Bạn đang làm việc với một đối tượng list (danh sách).
    • Bạn không cần giữ lại thứ tự ban đầu của danh sách và muốn tiết kiệm bộ nhớ bằng cách thực hiện sắp xếp ngay tại chỗ. Đây thường là lựa chọn hiệu quả hơn về mặt bộ nhớ cho danh sách lớn.
  • Sử dụng sorted() khi:
    • Bạn muốn sắp xếp bất kỳ đối tượng iterable nào (tuple, chuỗi, set, dictionary, generator, v.v.).
    • Bạn cần nhận được một danh sách mới đã sắp xếp mà không làm thay đổi đối tượng gốc. Điều này rất hữu ích khi bạn cần giữ lại dữ liệu ban đầu.

Hiểu rõ sự khác biệt này sẽ giúp bạn chọn đúng công cụ và viết code Python hiệu quả hơn!

Comments

There are no comments at the moment.