🔥 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 — Zadanie rekrutacyjne #8

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 Ordinal, OrdinalIgnoreCase, CurrentCulture
  • 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

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.”


Podsumowanie

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

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

Programista .NET z doświadczeniem komercyjnym w firmach takich jak COIG, Kamsoft 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ć.

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ę →