CodeGym /Kursy /C# SELF /Sortowanie kolekcji

Sortowanie kolekcji

C# SELF
Poziom 29, Lekcja 2
Dostępny

1. Wprowadzenie

Jeśli kiedykolwiek miałeś szufladę ze skarpetkami i próbowałeś znaleźć parę, już spotkałeś się z problemem sortowania. Gdy wszystko jest w bałaganie, znalezienie właściwej rzeczy zamienia się w quest. W programowaniu z kolekcjami — to samo.

Sortowanie — to proces układania elementów kolekcji według określonego kryterium (np. alfabetycznie, według wartości, daty itd.). To ważne dla:

  • Wyświetlania danych użytkownikowi (nikt nie lubi chaosu).
  • Ułatwienia wyszukiwania (np. wyszukiwanie binarne działa tylko na posortowanych kolekcjach).
  • Porównywania, raportów, eksportu i innych operacji.

Pytanie za milion: "A jeśli mam tylko pięć elementów, mogę nie sortować?" — Teoretycznie tak, ale jak tylko pięć zamieni się w pięćset albo pięć tysięcy, bez automatyzacji się nie obejdzie.

W .NET są dwa główne podejścia do sortowania:

  • Zmiana oryginalnej kolekcji (np. sortowanie List<T> metodą Sort).
  • Tworzenie nowej posortowanej kopii kolekcji (np. klonowanie tablicy, a potem sortowanie).

Sortowanie listy metodą .Sort()

Metoda Sort() jest w klasie List<T>, bo ta klasa implementuje interfejs IList<T>, dając dostęp po indeksie i możliwość zmiany kolejności elementów.

Przykład — sortowanie liczb rosnąco:


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("Sortowanie rosnąco:");
        foreach(var number in numbers)
        {
            Console.Write($"{number} "); // 1 2 5 5 6 9
        }
    }
}

Tutaj sortowanie jest "na miejscu": oryginalna lista numbers się zmienia, elementy są przestawiane.

Sortowanie stringów

Sortowanie świetnie działa też dla stringów:


var words = new List<string> { "pomarańcza", "jabłko", "banan", "gruszka" };
words.Sort();
Console.WriteLine(string.Join(", ", words)); // banan, gruszka, jabłko, pomarańcza

Ciekawostka: domyślnie sortowanie stringów idzie według porządku Unicode, a nie "ludzkiego" alfabetu (szczególnie przy różnych językach, miej to na uwadze przy aplikacjach wielojęzycznych).

2. Sortowanie według własnych zasad

Czasem sortowanie "domyślne" nie pasuje. Załóżmy, że chcesz posortować użytkowników nie po imieniu, a po wieku albo dacie rejestracji.

Sortowanie z użyciem wyrażenia lambda (metoda Sort(Comparison<T>))

Metodę Sort można wywołać tak, żeby przekazać specjalną zasadę sortowania — funkcję porównującą dwa elementy.

Sortowanie użytkowników po wieku:


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

// ...

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

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

foreach (var user in users)
{
    Console.WriteLine($"{user.Name}: {user.Age}");
}
// Wynik:
// Bob: 25
// Alicja: 30
// Ewa: 35

Jak działa ta magia? Lambda (u1, u2) => u1.Age.CompareTo(u2.Age) zwraca liczbę ujemną, jeśli u1 jest młodszy, dodatnią — jeśli starszy, i 0, jeśli wiek jest taki sam.

Użycie interfejsu IComparer<T>

Czasem chcesz wynieść zasadę sortowania do osobnej klasy, np. jeśli masz dużo kolekcji i złożoną logikę.


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()); // teraz sortowanie po wieku

To wygodne, jeśli sortowanie jest potrzebne w różnych miejscach programu albo masz wiele różnych sortowań.

Sortowanie po kilku polach ręcznie

Jeśli niektórzy użytkownicy mają ten sam wiek, ale chcesz ich posortować po imieniu w ramach tej samej grupy wiekowej:


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

Teraz sortowanie najpierw po wieku, potem — po imieniu.

3. Sortowanie kopii kolekcji (bez zmiany oryginału)

Jeśli nie chcesz zmieniać oryginalnej listy, skopiuj ją przed sortowaniem:


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

Dla własnych obiektów — to samo:


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

4. Sortowanie tablic

Z tablicami (T[]) też jest prosto:


int[] numbers = { 4, 2, 9, 7 };
Array.Sort(numbers); // zmienia się oryginalna tablica

Dla "customowego" sortowania:


Array.Sort(numbers, (a, b) => b.CompareTo(a)); // sortowanie malejąco

Zwróć uwagę, że Array.Sort zmienia oryginalną tablicę, a nie zwraca nową. Jeśli chcesz zachować oryginał, skopiuj tablicę wcześniej:


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

5. "Sortowanie" słowników (Dictionary<TKey, TValue>)

Dictionary<TKey, TValue> — to z natury nieuporządkowana kolekcja (czyli nie gwarantuje kolejności przy enumeracji). Ale czasem chcesz dostać "posortowane pary".

Żeby dostać posortowane klucze lub wartości, stwórz listę, skopiuj tam pary i posortuj jak chcesz:


var dict = new Dictionary<string, int>
{
    { "jabłko", 2 },
    { "pomarańcza", 5 },
    { "gruszka", 3 }
};

// Sortowanie po kluczu:
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}");
}

// Sortowanie po wartości:
keyValueList.Sort((a, b) => a.Value.CompareTo(b.Value));
foreach (var kv in keyValueList)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

Jeśli chcesz dostać posortowaną listę kluczy lub wartości:


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

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

Ale pamiętaj, że struktura Dictionary się nie zmienia — po prostu dostajesz posortowaną enumerację. W .NET 9 pojawi się OrderedDictionary<TKey, TValue>, który zachowuje kolejność elementów. Rzadko, ale czasem taka struktura się przydaje.

6. Przydatne niuanse

Czy trzeba implementować interfejs porównywania?

Prosto: jeśli chcesz, żeby twoje obiekty "wiedziały", jak się porównywać (np. po dacie albo imieniu), zaimplementuj interfejs 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);
    }
}

Teraz możesz zrobić:


var products = new List<Product> { /* ... */ };
products.Sort(); // sortuje po cenie

Jeśli domyślne porównanie ci nie pasuje — użyj IComparer<T> albo przekaż lambdę, jak wyżej.

Sortowanie listy użytkowników alfabetycznie

Załóżmy, że w aplikacji masz listę użytkowników:


List<string> users = new List<string> { "Wiktor", "Anna", "Katarzyna", "Borys" };
users.Sort(); // teraz users jest posortowany: Anna, Borys, Katarzyna, Wiktor

Rozdzielanie zadań według pilności


class Task
{
    public string Title { get; set; }
    public int Priority { get; set; } // 1 - pilne, 2 - ważne, 3 - można odłożyć
}

var todo = new List<Task>
{
    new Task { Title = "Zrobić zadanie domowe", Priority = 2 },
    new Task { Title = "Kupić chleb", Priority = 1 },
    new Task { Title = "Obejrzeć serial", Priority = 3 }
};

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

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

Porównanie sposobów sortowania

Kolekcja Czy zmienia się na miejscu? Metoda sortowania Czy można ustawić zasadę?
List<T>
Tak
Sort()
Tak: lambda lub IComparer
T[]
Tak
Array.Sort()
Tak: lambda lub IComparer
Dictionary<TKey, TValue>
Nie — (tworzymy listę i sortujemy) Tak: przez lambdę lub IComparer

7. Typowe błędy i niuanse sortowania

Sortowanie na miejscu — zmienia oryginalną kolekcję! Jeśli oryginalne dane nie mogą się zmienić — skopiuj je wcześniej.

Próba sortowania niezmiennej kolekcji (np. ReadOnlyCollection<T>) skończy się błędem w czasie działania.

Porównywanie stringów różnie na różnych kulturach: sortowanie stringów (szczególnie z cyrylicą, umlautami itd.) może się różnić na różnych lokalizacjach. Dla poprawnego sortowania użyj Comparer.Create(...) z odpowiednią kulturą.

Sortowanie słowników nie zmienia ich struktury — zawsze dostajesz nową sekwencję par (albo nową listę).

Dla złożonego porządku użyj interfejsu IComparer<T> z własną logiką.

Oto przykład złego sortowania (rozbij na praktyczne pułapki):


var numbers = new List<int> { 1, 2, 3 };
var sorted = numbers.Sort(); // BŁĄD: Sort() zwraca void!

Poprawnie:


numbers.Sort(); // zmienia numbers na miejscu

// Jeśli chcesz nową kolekcję:
var sorted = new List<int>(numbers);
sorted.Sort();
2
Zadanie
C# SELF, poziom 29, lekcja 2
Niedostępne
Sortowanie listy stringów w porządku alfabetycznym
Sortowanie listy stringów w porządku alfabetycznym
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION