🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Sortowanie nazw (stringów) w C#

Czego szuka rekruter: StringComparer, porównanie kulturowe, Array.Sort vs LINQ, stabilność sortowania
Poziom: Junior


Zadanie

Posortuj tablicę nazw (stringów) alfabetycznie — rosnąco i malejąco.


Czego szuka rekruter

Sortowanie stringów to pozornie proste zadanie z ukrytymi pułapkami. Rekruter sprawdza:

  • Czy kandydat wie, że domyślne porównanie stringów jest Ordinal — “Z” < “a” (wielkie litery mają niższy kod ASCII)
  • Czy zna StringComparer i różnicę między OrdinalOrdinalIgnoreCaseCurrentCulture
  • Czy rozumie, że Array.Sort modyfikuje oryginalną tablicę, a LINQ OrderBy tworzy nową
  • null w tablicy — StringComparer obsługuje to inaczej niż porównanie operatorem

Pułapka — sortowanie wielkość-liter-świadome

var names = new[] { "anna", "Zofia", "marek", "Beata" };
Array.Sort(names);  // Ordinal domyślnie

// Wynik: "Beata", "Zofia", "anna", "marek"
// Bo 'B' (66) < 'Z' (90) < 'a' (97) < 'm' (109)
// Wielkie litery wchodzą przed małymi!

✅ Poprawne sortowanie — kulturowo-świadome, ignorując wielkość

static string[] SortNames(string[] names, bool ascending = true)
{
    if (names == null) throw new ArgumentNullException(nameof(names));

    var comparer = StringComparer.CurrentCultureIgnoreCase;

    return ascending
        ? names.OrderBy(n => n, comparer).ToArray()
        : names.OrderByDescending(n => n, comparer).ToArray();
}

var names = new[] { "anna", "Zofia", "marek", "Beata", null };
var sorted = SortNames(names);
// Wynik: null, "anna", "Beata", "marek", "Zofia"

Kiedy który StringComparer?

// Ordinal — szybkie, bajt po bajcie, case-sensitive
// Używaj dla: kluczy słownika, identyfikatorów, porównań binarne-równe
StringComparer.Ordinal

// OrdinalIgnoreCase — szybkie, bajt po bajcie, case-insensitive
// Używaj dla: nazw plików, HTTP headerów, kluczy config
StringComparer.OrdinalIgnoreCase

// CurrentCulture — kulturowo-świadome (ą, ę, ó...), case-sensitive
// Używaj dla: danych wyświetlanych użytkownikowi w jego języku
StringComparer.CurrentCulture

// CurrentCultureIgnoreCase — kulturowo-świadome, case-insensitive
// Używaj dla: sortowania nazw dla użytkownika
StringComparer.CurrentCultureIgnoreCase

// InvariantCulture — kultura neutralna, deterministyczna między środowiskami
// Używaj dla: danych do zapisu/odczytu z pliku, API
StringComparer.InvariantCulture

Modyfikacja in-place vs nowa tablica

var names = new[] { "marek", "anna", "zofia" };

// Array.Sort — modyfikuje oryginalną tablicę
Array.Sort(names, StringComparer.CurrentCultureIgnoreCase);
// names jest teraz: ["anna", "marek", "zofia"]

// LINQ OrderBy — nie modyfikuje, zwraca nową sekwencję
var sorted = names.OrderBy(n => n, StringComparer.CurrentCultureIgnoreCase).ToArray();
// names niezmienione, sorted to nowa tablica

Malejąco z Array.Sort:

Array.Sort(names, StringComparer.CurrentCultureIgnoreCase);
Array.Reverse(names);  // Odwróć po sortowaniu rosnącym

Stabilność sortowania — dlaczego to ma znaczenie

Sortowanie jest stabilne, jeśli elementy uznane za “równe” zachowują swoją oryginalną względną kolejność po posortowaniu. To niepozorna właściwość, która staje się istotna, gdy sortujesz po jednym kluczu, ale kolejność w ramach tego samego klucza wciąż ma znaczenie — np. lista osób posortowana najpierw po dacie rejestracji, a potem (stabilnie) po imieniu, tak by przy identycznych imionach zachować kolejność rejestracji.

var osoby = new[]
{
    (Imie: "Anna", DataRejestracji: 3),
    (Imie: "Anna", DataRejestracji: 1),
    (Imie: "Marek", DataRejestracji: 2),
};

// LINQ OrderBy jest gwarantowane stabilne — przy remisie zachowuje kolejność wejściową
var poImieniu = osoby.OrderBy(o => o.Imie, StringComparer.CurrentCultureIgnoreCase).ToArray();
// obie "Anna" zostają w kolejności: DataRejestracji 3, potem 1 (tak jak w wejściu)

Kluczowa różnica: LINQ OrderBy/OrderByDescending jest gwarantowane stabilne w dokumentacji .NET — możesz na tym polegać. Array.Sort nie gwarantuje stabilności — wewnętrznie używa introspektywnego sortowania (hybryda quicksort/heapsort/insertion sort), które przy remisach może przestawić kolejność elementów w sposób niezdefiniowany. Jeśli stabilność ma znaczenie dla Twojego przypadku (a przy sortowaniu ludzkich danych — nazwisk, dat — często ma), wybierz LINQ, nie Array.Sort.


Testy jednostkowe

[Fact]
public void SortNames_Ascending_IgnoresCase()
{
    var result = SortNames(new[] { "Zofia", "anna", "Beata", "marek" });
    Assert.Equal(new[] { "anna", "Beata", "marek", "Zofia" }, result);
}

[Fact]
public void SortNames_Descending_ReturnsReverseOrder()
{
    var result = SortNames(new[] { "Zofia", "anna", "Beata" }, ascending: false);
    Assert.Equal(new[] { "Zofia", "Beata", "anna" }, result);
}

[Fact]
public void SortNames_WithNulls_NullsFirst()
{
    var result = SortNames(new[] { "marek", null, "anna" });
    Assert.Null(result[0]);
}

[Fact]
public void SortNames_EmptyArray_ReturnsEmpty()
{
    Assert.Empty(SortNames(Array.Empty<string>()));
}

Co powiedzieć na rozmowie

“Domyślne sortowanie stringów w C# to Ordinal — porównanie bajt po bajcie, gdzie wielkie litery (‘A’=65) są przed małymi (‘a’=97). Dla sortowania nazw użytkowników chcę CurrentCultureIgnoreCase żeby ‘anna’ i ‘Anna’ były traktowane jako równe i żeby polskie znaki (ą, ę) były posortowane poprawnie. LINQ OrderBy jest bezpieczniejszy bo nie mutuje oryginalnej tablicy i, w przeciwieństwie do Array.Sort, jest gwarantowane stabilne.”


Podsumowanie

Kluczowe decyzje: który StringComparer (zależy od kontekstu), Array.Sort (in-place, niestabilne) vs LINQ (nowa sekwencja, gwarantowanie stabilne). Dla danych użytkownika zawsze CurrentCulture lub CurrentCultureIgnoreCase.

Powiązane: to zadanie dotyka porównywania i sortowania — zobacz algorytm sortowania bąbelkowego i alternatywę z LINQ. Teoria stringów: łańcuchy znaków.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

2 comments

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →