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

Zliczanie liter w tekście — Zadanie rekrutacyjne #9

Czego szuka rekruter: Dictionary<T,int>, LINQ GroupBy, obsługa wielkości liter i znaków
Poziom: Junior


Zadanie

Napisz funkcję, która liczy wystąpienia każdego znaku (lub litery) w podanym tekście i zwraca wyniki posortowane od najczęstszego.


Czego szuka rekruter

To zadanie o wzorcu zliczania z Dictionary — fundamentalny pattern w algorytmice. Rekruter sprawdza:

  • Znajomość Dictionary<char, int> lub Dictionary<char, int> z GetValueOrDefault
  • Czy kandydat pyta o szczegóły: tylko litery czy wszystkie znaki? Wielkość liter?
  • Czy zna LINQ GroupBy jako alternatywę
  • Co zwrócić — Dictionary, posortowane pary, lista wynikowa?

Decyzje projektowe do omówienia z rekruterem

1. Litery tylko czy wszystkie znaki (spacje, cyfry, interpunkcja)?
2. Case-sensitive? ("A" i "a" liczone razem czy osobno?)
3. Wynik posortowany po częstości czy alfabetycznie?
4. Co dla null/pustego stringa?

✅ Rozwiązanie z Dictionary

static Dictionary<char, int> CountCharacters(string text, bool ignoreCase = true)
{
    if (string.IsNullOrEmpty(text))
        return new Dictionary<char, int>();
    
    var counts = new Dictionary<char, int>();
    
    foreach (char c in text)
    {
        // Opcjonalnie: pomijaj spacje i interpunkcję
        // if (!char.IsLetter(c)) continue;
        
        char key = ignoreCase ? char.ToLowerInvariant(c) : c;
        counts[key] = counts.GetValueOrDefault(key) + 1;
    }
    
    return counts;
}

// Posortowane wyniki:
static IEnumerable<(char Char, int Count)> CountSorted(string text)
    => CountCharacters(text)
        .OrderByDescending(kv => kv.Value)
        .ThenBy(kv => kv.Key)
        .Select(kv => (kv.Key, kv.Value));

// Użycie:
foreach (var (ch, count) in CountSorted("programowanie"))
    Console.WriteLine($"'{ch}': {count}");
// 'a': 3
// 'o': 2
// 'e': 1
// 'g': 1
// ...

GetValueOrDefault(key) zwraca 0 jeśli klucz nie istnieje — eliminuje potrzebę sprawdzania ContainsKey.


Alternatywa — LINQ GroupBy

static IEnumerable<(char Char, int Count)> CountWithLinq(string text, bool ignoreCase = true)
{
    if (string.IsNullOrEmpty(text))
        return Enumerable.Empty<(char, int)>();
    
    return text
        .Select(c => ignoreCase ? char.ToLowerInvariant(c) : c)
        .GroupBy(c => c)
        .OrderByDescending(g => g.Count())
        .Select(g => (g.Key, g.Count()));
}

Czytelniejsze ale mniej wydajne — Count() wywołane wielokrotnie. Możesz użyć g.Count() raz i zapisać.


Wersja tylko dla liter (pomijanie cyfr, spacji, interpunkcji)

static Dictionary<char, int> CountLettersOnly(string text)
{
    var counts = new Dictionary<char, int>();
    
    foreach (char c in text ?? string.Empty)
    {
        if (!char.IsLetter(c)) continue;
        
        char lower = char.ToLowerInvariant(c);
        counts[lower] = counts.GetValueOrDefault(lower) + 1;
    }
    
    return counts;
}

Testy jednostkowe

[Fact]
public void CountCharacters_IgnoresCase_MergesLetters()
{
    var result = CountCharacters("AaB");
    Assert.Equal(2, result['a']);
    Assert.Equal(1, result['b']);
}

[Fact]
public void CountCharacters_EmptyString_ReturnsEmptyDictionary()
{
    Assert.Empty(CountCharacters(""));
}

[Fact]
public void CountCharacters_CountsCorrectly()
{
    var result = CountCharacters("aabbbc");
    Assert.Equal(2, result['a']);
    Assert.Equal(3, result['b']);
    Assert.Equal(1, result['c']);
}

[Fact]
public void CountSorted_ReturnsOrderedByFrequency()
{
    var sorted = CountSorted("aabbc").ToList();
    Assert.Equal('a', sorted[0].Char);
    Assert.Equal(2, sorted[0].Count);
}

Co powiedzieć na rozmowie

Dictionary<char, int> z GetValueOrDefault jest wzorcem zliczania — prosty i O(n). Przed napisaniem pytam o szczegóły: czy liczymy wszystkie znaki czy tylko litery, czy case-sensitive, jak sortować wyniki. To pokazuje że myślę o wymaganiach zanim zacznę kodować.”


Podsumowanie

Wzorzec: counts[key] = counts.GetValueOrDefault(key) + 1. Stary idiom ContainsKey + Add/increment to dwie operacje zamiast jednej. LINQ GroupBy jest alternatywą, ale warto wiedzieć jak to działa pod spodem.

👨‍💻
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ć.

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