CodeGym /Các khóa học /C# SELF /Sắp xếp collection

Sắp xếp collection

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Nếu bạn từng có một cái ngăn kéo đầy tất và phải mò tìm một đôi, thì bạn đã gặp vấn đề sắp xếp rồi đấy. Khi mọi thứ lộn xộn, việc tìm cái mình cần đúng là một cuộc phiêu lưu. Lập trình với collection cũng y chang vậy luôn.

Sắp xếp là quá trình sắp đặt các phần tử của collection theo một tiêu chí nào đó (ví dụ theo alphabet, theo giá trị, ngày tháng, v.v.). Việc này quan trọng vì:

  • Hiển thị dữ liệu cho user (không ai thích lộn xộn cả).
  • Dễ tìm kiếm hơn (ví dụ binary search chỉ dùng được với collection đã sort).
  • So sánh, báo cáo, export và các thao tác khác.

Câu hỏi triệu đô: "Nếu mình chỉ có năm phần tử thôi, khỏi sort được không?" — Lý thuyết thì được, nhưng khi năm thành năm trăm hay năm ngàn, không tự động thì toang luôn.

Trong .NET có hai cách chính để sort:

  • Thay đổi collection gốc (ví dụ sort List<T> bằng method Sort).
  • Tạo một bản copy đã sort của collection (ví dụ clone array rồi sort).

Sắp xếp list bằng method .Sort()

Method Sort() có trong class List<T>, vì class này implement interface IList<T>, cho phép truy cập theo index và thay đổi thứ tự phần tử.

Ví dụ — sort số tăng dần:


using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var numbers = new List<int> { 5, 2, 9, 1, 5, 6 };
        numbers.Sort();
        Console.WriteLine("Sắp xếp tăng dần:");
        foreach(var number in numbers)
        {
            Console.Write($"{number} "); // 1 2 5 5 6 9
        }
    }
}

Ở đây sort "tại chỗ": list gốc numbers bị thay đổi, các phần tử bị đảo lại.

Sắp xếp string

Sort cũng chạy ngon với string luôn:


var words = new List<string> { "cam", "táo", "chuối", "lê" };
words.Sort();
Console.WriteLine(string.Join(", ", words)); // cam, chuối, lê, táo

Fun fact: mặc định sort string theo thứ tự Unicode, không phải alphabet "con người" (đặc biệt với nhiều ngôn ngữ, nhớ chú ý khi làm app đa ngôn ngữ nhé).

2. Sắp xếp theo quy tắc riêng

Đôi khi sort mặc định không hợp lý. Ví dụ, bạn muốn sort user không theo tên mà theo tuổi hoặc ngày đăng ký.

Sắp xếp bằng lambda expression (method Sort(Comparison<T>))

Method Sort có thể gọi với một function so sánh hai phần tử — chính là quy tắc sort custom.

Sort user theo tuổi:


class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// ...

var users = new List<User>
{
    new User { Name = "Alysa", Age = 30 },
    new User { Name = "Bob", Age = 25 },
    new User { Name = "Eva", Age = 35 }
};

users.Sort((u1, u2) => u1.Age.CompareTo(u2.Age));

foreach (var user in users)
{
    Console.WriteLine($"{user.Name}: {user.Age}");
}
// Output:
// Bob: 25
// Alysa: 30
// Eva: 35

Cách này hoạt động thế nào? Lambda (u1, u2) => u1.Age.CompareTo(u2.Age) trả về số âm nếu u1 nhỏ tuổi hơn, số dương nếu lớn hơn, và 0 nếu bằng tuổi.

Dùng interface IComparer<T>

Đôi khi bạn muốn tách quy tắc sort ra class riêng, ví dụ khi có nhiều collection và logic sort phức tạp.


class UserAgeComparer : IComparer<User>
{
    public int Compare(User x, User y)
    {
        return x.Age.CompareTo(y.Age);
    }
}

// ...

var users = new List<User>{ /* ... */ };
users.Sort(new UserAgeComparer()); // giờ sort theo tuổi

Cách này tiện nếu cần sort ở nhiều chỗ khác nhau hoặc có nhiều kiểu sort.

Sắp xếp theo nhiều trường thủ công

Nếu vài user cùng tuổi, bạn muốn sort tiếp theo tên trong nhóm cùng tuổi:


users.Sort((u1, u2) => {
    int ageCompare = u1.Age.CompareTo(u2.Age);
    if (ageCompare != 0)
        return ageCompare;
    else
        return u1.Name.CompareTo(u2.Name);
});

Giờ sort trước theo tuổi, sau đó theo tên.

3. Sắp xếp bản copy của collection (không đổi gốc)

Nếu không muốn thay đổi list gốc, hãy copy nó trước khi sort:


var copy = new List<int>(numbers);
copy.Sort();

Với object custom cũng vậy:


var usersCopy = new List<User>(users);
usersCopy.Sort((a, b) => a.Age.CompareTo(b.Age));

4. Sắp xếp array

Với array (T[]) cũng đơn giản luôn:


int[] numbers = { 4, 2, 9, 7 };
Array.Sort(numbers); // array gốc bị thay đổi

Nếu muốn sort custom:


Array.Sort(numbers, (a, b) => b.CompareTo(a)); // sort giảm dần

Lưu ý Array.Sort thay đổi array gốc, không trả về array mới. Nếu muốn giữ bản gốc, copy trước:


int[] oldNumbers = { 3, 2, 1 };
int[] copy = (int[])oldNumbers.Clone();
Array.Sort(copy);

5. "Sắp xếp" dictionary (Dictionary<TKey, TValue>)

Dictionary<TKey, TValue> vốn là collection không có thứ tự (tức là không đảm bảo thứ tự khi duyệt). Nhưng đôi khi bạn muốn lấy "cặp đã sort".

Để lấy key hoặc value đã sort, tạo list, copy các cặp vào rồi sort theo ý:


var dict = new Dictionary<string, int>
{
    { "táo", 2 },
    { "cam", 5 },
    { "lê", 3 }
};

// Sort theo key:
var keyValueList = new List<KeyValuePair<string, int>>(dict);
keyValueList.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var kv in keyValueList)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

// Sort theo value:
keyValueList.Sort((a, b) => a.Value.CompareTo(b.Value));
foreach (var kv in keyValueList)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

Nếu muốn lấy list key hoặc value đã sort:


var sortedKeys = new List<string>(dict.Keys);
sortedKeys.Sort();

var sortedValues = new List<int>(dict.Values);
sortedValues.Sort();

Nhưng nhớ là structure Dictionary không đổi — bạn chỉ lấy được list đã sort thôi. Trong .NET 9 sẽ có OrderedDictionary<TKey, TValue>, nó giữ thứ tự phần tử. Không phải lúc nào cũng cần, nhưng đôi khi lại rất tiện.

6. Một số lưu ý hay ho

Bắt buộc phải implement interface so sánh không?

Dễ thôi: nếu bạn muốn object của mình "biết" cách so sánh (ví dụ theo ngày hoặc tên), hãy implement interface IComparable<T>.


class Product : IComparable<Product>
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public int CompareTo(Product other)
    {
        return Price.CompareTo(other.Price);
    }
}

Giờ bạn có thể làm:


var products = new List<Product> { /* ... */ };
products.Sort(); // sort theo giá

Nếu không thích sort mặc định — dùng IComparer<T> hoặc truyền lambda như trên.

Sắp xếp list user theo alphabet

Giả sử app có list user:


List<string> users = new List<string> { "Viktor", "Anna", "Ekaterina", "Boris" };
users.Sort(); // giờ users đã sort: Anna, Boris, Viktor, Ekaterina

Phân loại task theo độ gấp


class Task
{
    public string Title { get; set; }
    public int Priority { get; set; } // 1 - gấp, 2 - quan trọng, 3 - có thể để sau
}

var todo = new List<Task>
{
    new Task { Title = "Làm bài tập về nhà", Priority = 2 },
    new Task { Title = "Mua bánh mì", Priority = 1 },
    new Task { Title = "Xem phim", Priority = 3 }
};

todo.Sort((a, b) => a.Priority.CompareTo(b.Priority));

foreach (var task in todo)
    Console.WriteLine($"{task.Priority}: {task.Title}");

So sánh các cách sort

Collection Có thay đổi tại chỗ không? Method để sort Có custom rule được không?
List<T>
Sort()
Có: lambda hoặc IComparer
T[]
Array.Sort()
Có: lambda hoặc IComparer
Dictionary<TKey, TValue>
Không — (tạo list rồi sort) Có: qua lambda hoặc IComparer

7. Lỗi phổ biến và lưu ý khi sort

Sort tại chỗ — sẽ thay đổi collection gốc! Nếu không muốn mất dữ liệu gốc — hãy copy trước.

Thử sort collection không cho thay đổi (ví dụ ReadOnlyCollection<T>) sẽ bị lỗi runtime.

So sánh string khác nhau ở các culture: sort string (đặc biệt có ký tự Cyrillic, umlaut, v.v.) có thể khác nhau ở từng locale. Để sort chuẩn, dùng Comparer.Create(...) với culture phù hợp.

Sort dictionary không đổi structure — luôn trả về sequence mới (hoặc list mới).

Muốn sort phức tạp thì dùng interface IComparer<T> với logic riêng.

Ví dụ sort sai (điển hình cho newbie):


var numbers = new List<int> { 1, 2, 3 };
var sorted = numbers.Sort(); // SAI: Sort() trả về void!

Đúng phải là:


numbers.Sort(); // thay đổi numbers tại chỗ

// Nếu muốn collection mới:
var sorted = new List<int>(numbers);
sorted.Sort();
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION