Kolekcje generyczne – List<T> i Dictionary<K,V>
Poznasz potężne struktury danych, które zastępują tablice. Nauczysz się przechowywać, wyszukiwać i przetwarzać dane w elastyczny sposób!
Problem – ograniczenia tablic
Tablice w C# mają stały rozmiar. Co jeśli nie wiesz ile elementów będziesz przechowywać?
// 😰 Tablica ma STAŁY rozmiar string[] uczniowie = new string[3]; uczniowie[0] = "Jan"; uczniowie[1] = "Anna"; uczniowie[2] = "Piotr"; // Chcemy dodać czwartego ucznia... uczniowie[3] = "Maria"; // 💥 IndexOutOfRangeException! // Trzeba tworzyć nową, większą tablicę i kopiować 😞 string[] nowaTab = new string[4]; Array.Copy(uczniowie, nowaTab, uczniowie.Length); nowaTab[3] = "Maria";
- Stały rozmiar – trzeba znać liczbę elementów z góry
- Brak wbudowanych metod – usuwanie, wstawianie, wyszukiwanie
- Brak dostępu po kluczu – tylko po indeksie liczbowym
Co jeśli chcemy…
| Potrzeba | Tablica | Rozwiązanie |
|---|---|---|
| Dodawać elementy dynamicznie | ❌ Stały rozmiar | List<T> |
| Usuwać z dowolnego miejsca | ❌ Ręczne przesuwanie | List<T> |
| Szukać po kluczu (np. PESEL → Osoba) | ❌ Tylko indeks | Dictionary<K,V> |
| Sprawdzić czy element istnieje | ❌ Pętla ręczna | Contains() |
Czym są kolekcje generyczne?
Kolekcje generyczne to elastyczne struktury danych z przestrzeni System.Collections.Generic. Słowo „generyczne” oznacza, że określasz typ elementów przy tworzeniu.
using System.Collections.Generic; // Lista liczb całkowitych List<int> liczby = new List<int>(); // Lista stringów List<string> imiona = new List<string>(); // Lista własnych obiektów List<Uczen> uczniowie = new List<Uczen>(); // Słownik: klucz string → wartość int Dictionary<string, int> oceny = new Dictionary<string, int>();
Kolekcja generyczna to jak pudełko z etykietą:
List<int>= pudełko z etykietą „tylko liczby całkowite”List<string>= pudełko z etykietą „tylko teksty”- Kompilator pilnuje, żebyś nie włożył czegoś złego!
- Bezpieczeństwo typów – błędy wykrywane przy kompilacji
- Wydajność – brak boxing/unboxing
- Dynamiczny rozmiar – rośnie automatycznie
- Wbudowane metody – Add, Remove, Find, Sort…
Najważniejsze kolekcje
| Kolekcja | Opis | Użycie |
|---|---|---|
List<T> | Dynamiczna lista | Tablica o zmiennym rozmiarze |
Dictionary<K,V> | Słownik klucz-wartość | Szybkie wyszukiwanie po kluczu |
HashSet<T> | Zbiór unikalnych | Bez duplikatów |
Queue<T> | Kolejka FIFO | Pierwszy wchodzi, pierwszy wychodzi |
Stack<T> | Stos LIFO | Ostatni wchodzi, pierwszy wychodzi |
List<T> – dynamiczna lista
List<T> to dynamiczna tablica – rośnie automatycznie gdy dodajesz elementy. To najczęściej używana kolekcja!
using System.Collections.Generic; // Tworzenie pustej listy List<string> imiona = new List<string>(); // Dodawanie elementów imiona.Add("Jan"); imiona.Add("Anna"); imiona.Add("Piotr"); // Dostęp przez indeks (jak tablica) Console.WriteLine(imiona[0]); // Jan Console.WriteLine(imiona[1]); // Anna // Liczba elementów Console.WriteLine(imiona.Count); // 3 // Iteracja foreach (string imie in imiona) { Console.WriteLine(imie); }
Inicjalizacja z wartościami
// Inicjalizacja z wartościami (collection initializer) List<int> liczby = new List<int> { 1, 2, 3, 4, 5 }; // Skrócona składnia (C# 9+) List<string> owoce = new() { "Jabłko", "Gruszka", "Banan" }; // Z tablicy int[] tablica = { 10, 20, 30 }; List<int> lista = new List<int>(tablica); // Lista obiektów List<Uczen> uczniowie = new List<Uczen> { new Uczen { Imie = "Jan", Wiek = 17 }, new Uczen { Imie = "Anna", Wiek = 16 } };
List<T> – metody
| Metoda | Opis | Przykład |
|---|---|---|
Add(item) | Dodaje na końcu | lista.Add("X") |
AddRange(collection) | Dodaje wiele elementów | lista.AddRange(tablica) |
Insert(index, item) | Wstawia na pozycji | lista.Insert(0, "X") |
Remove(item) | Usuwa pierwszy pasujący | lista.Remove("X") |
RemoveAt(index) | Usuwa na pozycji | lista.RemoveAt(0) |
Clear() | Usuwa wszystko | lista.Clear() |
Contains(item) | Czy zawiera? | lista.Contains("X") |
IndexOf(item) | Pozycja elementu | lista.IndexOf("X") |
Count | Liczba elementów | lista.Count |
ToArray() | Konwersja na tablicę | lista.ToArray() |
List<string> owoce = new List<string> { "Jabłko", "Gruszka" }; // Dodawanie owoce.Add("Banan"); // Na końcu owoce.Insert(0, "Pomarańcza"); // Na początku owoce.AddRange(new[] { "Kiwi", "Mango" }); // Wiele naraz // Stan: Pomarańcza, Jabłko, Gruszka, Banan, Kiwi, Mango // Usuwanie owoce.Remove("Gruszka"); // Usuwa pierwszy "Gruszka" owoce.RemoveAt(0); // Usuwa pierwszy element // Sprawdzanie bool maJablko = owoce.Contains("Jabłko"); // true int pozycja = owoce.IndexOf("Banan"); // 1 (lub -1 jeśli brak) int ile = owoce.Count; // 4 // Konwersja na tablicę string[] tablica = owoce.ToArray(); // Czyszczenie owoce.Clear(); // Pusta lista, Count = 0
List<T> – wyszukiwanie
List<int> liczby = new List<int> { 5, 12, 3, 18, 7, 22, 9 }; // Find – znajdź PIERWSZY pasujący int pierwszyWiekszy = liczby.Find(n => n > 10); // 12 // FindAll – znajdź WSZYSTKIE pasujące List<int> wszystkieWieksze = liczby.FindAll(n => n > 10); // [12, 18, 22] // FindIndex – pozycja pierwszego pasującego int indeks = liczby.FindIndex(n => n > 10); // 1 // FindLast – ostatni pasujący int ostatni = liczby.FindLast(n => n > 10); // 22 // Exists – czy istnieje pasujący? bool czyJest = liczby.Exists(n => n > 100); // false // TrueForAll – czy wszystkie spełniają warunek? bool wszystkieDodatnie = liczby.TrueForAll(n => n > 0); // true
Wyszukiwanie obiektów
public class Produkt { public string Nazwa { get; set; } public decimal Cena { get; set; } } List<Produkt> produkty = new List<Produkt> { new Produkt { Nazwa = "Laptop", Cena = 3500 }, new Produkt { Nazwa = "Myszka", Cena = 80 }, new Produkt { Nazwa = "Monitor", Cena = 1200 } }; // Znajdź produkt po nazwie Produkt laptop = produkty.Find(p => p.Nazwa == "Laptop"); // Znajdź wszystkie drogie (powyżej 1000 zł) List<Produkt> drogie = produkty.FindAll(p => p.Cena > 1000); // Znajdź najtańszy (pętlą) Produkt najtanszy = produkty[0]; foreach (var p in produkty) { if (p.Cena < najtanszy.Cena) najtanszy = p; }
List<T> – sortowanie
List<int> liczby = new List<int> { 5, 2, 8, 1, 9 }; // Sortowanie rosnąco liczby.Sort(); // [1, 2, 5, 8, 9] // Sortowanie malejąco liczby.Reverse(); // [9, 8, 5, 2, 1] // Sortowanie stringów List<string> imiona = new List<string> { "Zofia", "Anna", "Jan" }; imiona.Sort(); // Anna, Jan, Zofia (alfabetycznie)
Sortowanie obiektów
List<Produkt> produkty = new List<Produkt> { new Produkt { Nazwa = "Laptop", Cena = 3500 }, new Produkt { Nazwa = "Myszka", Cena = 80 }, new Produkt { Nazwa = "Monitor", Cena = 1200 } }; // Sortuj po cenie rosnąco produkty.Sort((a, b) => a.Cena.CompareTo(b.Cena)); // Myszka (80), Monitor (1200), Laptop (3500) // Sortuj po cenie malejąco produkty.Sort((a, b) => b.Cena.CompareTo(a.Cena)); // Sortuj po nazwie produkty.Sort((a, b) => a.Nazwa.CompareTo(b.Nazwa));
Dictionary<K,V> – słownik
Dictionary<K,V> to kolekcja par klucz-wartość. Pozwala błyskawicznie znaleźć wartość po kluczu. Działa jak prawdziwy słownik: słowo → definicja.
- Klucz = nazwisko osoby (unikalne!)
- Wartość = numer telefonu
- Szukasz po nazwisku → dostajesz numer natychmiast!
using System.Collections.Generic; // Tworzenie słownika: klucz string, wartość int Dictionary<string, int> wiek = new Dictionary<string, int>(); // Dodawanie par klucz-wartość wiek["Jan"] = 25; wiek["Anna"] = 30; wiek["Piotr"] = 22; // Odczyt po kluczu Console.WriteLine(wiek["Anna"]); // 30 // Liczba elementów Console.WriteLine(wiek.Count); // 3
Inicjalizacja z wartościami
// Inicjalizacja z wartościami Dictionary<string, string> stolice = new Dictionary<string, string> { { "Polska", "Warszawa" }, { "Niemcy", "Berlin" }, { "Francja", "Paryż" } }; // Alternatywna składnia (C# 6+) Dictionary<string, int> oceny = new Dictionary<string, int> { ["Jan"] = 5, ["Anna"] = 4, ["Piotr"] = 3 }; Console.WriteLine(stolice["Polska"]); // Warszawa Console.WriteLine(oceny["Anna"]); // 4
Dictionary – operacje
| Operacja | Metoda | Przykład |
|---|---|---|
| Dodaj/Ustaw | dict[key] = value | wiek["Jan"] = 25 |
| Dodaj (jeśli brak) | Add(key, value) | wiek.Add("Jan", 25) |
| Pobierz | dict[key] | wiek["Jan"] |
| Usuń | Remove(key) | wiek.Remove("Jan") |
| Wyczyść | Clear() | wiek.Clear() |
| Czy ma klucz? | ContainsKey(key) | wiek.ContainsKey("Jan") |
| Czy ma wartość? | ContainsValue(val) | wiek.ContainsValue(25) |
| Liczba | Count | wiek.Count |
| Klucze | Keys | wiek.Keys |
| Wartości | Values | wiek.Values |
Dictionary<string, decimal> ceny = new Dictionary<string, decimal>(); // Dodawanie ceny["Laptop"] = 2500; // Dodaje lub nadpisuje ceny.Add("Myszka", 50); // Tylko dodaje (błąd jeśli istnieje!) // Modyfikacja ceny["Laptop"] = 2300; // Zmiana wartości // Usuwanie ceny.Remove("Myszka"); // Usuwa parę // Sprawdzanie bool maLaptop = ceny.ContainsKey("Laptop"); // true bool maCene = ceny.ContainsValue(2300); // true // Wszystkie klucze i wartości foreach (string produkt in ceny.Keys) Console.WriteLine(produkt); foreach (decimal cena in ceny.Values) Console.WriteLine(cena);
dict.Add(key, value)– rzuca wyjątek jeśli klucz istnieje!dict[key] = value– dodaje lub nadpisuje (bezpieczniej)
Dictionary – bezpieczny dostęp
Odwołanie do nieistniejącego klucza rzuca KeyNotFoundException! Są bezpieczniejsze metody.
Dictionary<string, int> oceny = new Dictionary<string, int> { ["Jan"] = 5, ["Anna"] = 4 }; // ❌ NIEBEZPIECZNE – rzuca wyjątek! // int ocena = oceny["Piotr"]; // KeyNotFoundException! // ✅ Sposób 1: ContainsKey if (oceny.ContainsKey("Piotr")) { Console.WriteLine(oceny["Piotr"]); } else { Console.WriteLine("Brak oceny"); } // ✅ Sposób 2: TryGetValue (NAJLEPSZY!) if (oceny.TryGetValue("Piotr", out int ocena)) { Console.WriteLine($"Ocena: {ocena}"); } else { Console.WriteLine("Brak oceny"); } // ✅ Sposób 3: GetValueOrDefault (C# 7.1+) int wynik = oceny.GetValueOrDefault("Piotr", 0); // 0 jeśli brak
TryGetValue sprawdza i pobiera w jednej operacji – szybsze niż ContainsKey + odczyt!
Dictionary – zliczanie wystąpień
Klasyczne zastosowanie słownika: zliczanie ile razy coś wystąpiło. Klucz = element, Wartość = liczba wystąpień.
- Ile razy każda litera występuje w tekście?
- Ile razy każde słowo występuje w dokumencie?
- Ile zamówień złożył każdy klient?
- Ile głosów dostał każdy kandydat?
Przykład 1: Zliczanie liter
string tekst = "programowanie"; // Słownik: litera → liczba wystąpień Dictionary<char, int> licznik = new Dictionary<char, int>(); foreach (char litera in tekst) { if (licznik.ContainsKey(litera)) { // Litera już była – zwiększ licznik licznik[litera]++; } else { // Pierwsza litera – ustaw na 1 licznik[litera] = 1; } } // Wyświetl wyniki foreach (var para in licznik) { Console.WriteLine($"'{para.Key}' → {para.Value}x"); } // Wynik: // 'p' → 1x // 'r' → 2x // 'o' → 2x // 'g' → 1x // 'a' → 2x // 'm' → 1x // 'w' → 1x // 'n' → 1x // 'i' → 1x // 'e' → 1x
Skrócona wersja z TryGetValue
string tekst = "abrakadabra"; Dictionary<char, int> licznik = new Dictionary<char, int>(); foreach (char c in tekst) { // TryGetValue zwraca 0 jeśli klucz nie istnieje licznik.TryGetValue(c, out int count); licznik[c] = count + 1; } // Jeszcze krócej (C# 8+) foreach (char c in tekst) { licznik[c] = licznik.GetValueOrDefault(c, 0) + 1; } // Wynik dla "abrakadabra": // 'a' → 5x // 'b' → 2x // 'r' → 2x // 'k' → 1x // 'd' → 1x
Przykład 2: Zliczanie słów
string tekst = "to jest tekst który jest przykładem tego jak to działa"; string[] slowa = tekst.Split(' '); Dictionary<string, int> licznikSlow = new Dictionary<string, int>(); foreach (string slowo in slowa) { string male = slowo.ToLower(); // Ignoruj wielkość liter licznikSlow[male] = licznikSlow.GetValueOrDefault(male, 0) + 1; } // Znajdź najczęstsze słowo string najczestsze = ""; int maxCount = 0; foreach (var para in licznikSlow) { if (para.Value > maxCount) { maxCount = para.Value; najczestsze = para.Key; } } Console.WriteLine($"Najczęstsze: '{najczestsze}' ({maxCount}x)"); // Najczęstsze: 'jest' (2x) lub 'to' (2x)
Przykład 3: Głosowanie
string[] glosy = { "A", "B", "A", "C", "A", "B", "A", "C", "C", "C" }; Dictionary<string, int> wyniki = new Dictionary<string, int>(); foreach (string kandydat in glosy) { wyniki[kandydat] = wyniki.GetValueOrDefault(kandydat, 0) + 1; } // Wyświetl wyniki Console.WriteLine("=== WYNIKI GŁOSOWANIA ==="); foreach (var w in wyniki) { Console.WriteLine($"Kandydat {w.Key}: {w.Value} głosów"); } // Znajdź zwycięzcę string zwyciezca = ""; int maxGlosow = 0; foreach (var w in wyniki) { if (w.Value > maxGlosow) { maxGlosow = w.Value; zwyciezca = w.Key; } } Console.WriteLine($"Zwycięzca: {zwyciezca} ({maxGlosow} głosów)");
// Uniwersalny wzorzec:
foreach (var element in kolekcja)
{
slownik[element] = slownik.GetValueOrDefault(element, 0) + 1;
}
Dictionary – iteracja
Dictionary<string, int> oceny = new Dictionary<string, int> { ["Jan"] = 5, ["Anna"] = 4, ["Piotr"] = 3 }; // Sposób 1: foreach z KeyValuePair foreach (KeyValuePair<string, int> para in oceny) { Console.WriteLine($"{para.Key}: {para.Value}"); } // Sposób 2: foreach z var (krócej) foreach (var para in oceny) { Console.WriteLine($"{para.Key}: {para.Value}"); } // Sposób 3: dekonstrukcja (C# 7+) foreach ((string imie, int ocena) in oceny) { Console.WriteLine($"{imie}: {ocena}"); } // Tylko klucze foreach (string imie in oceny.Keys) { Console.WriteLine(imie); } // Tylko wartości foreach (int ocena in oceny.Values) { Console.WriteLine(ocena); }
Inne kolekcje
HashSet<T> – zbiór unikalnych
// HashSet – automatycznie usuwa duplikaty HashSet<int> unikalne = new HashSet<int>(); unikalne.Add(1); unikalne.Add(2); unikalne.Add(1); // Ignorowane – już jest! unikalne.Add(3); Console.WriteLine(unikalne.Count); // 3 (nie 4!) // Szybkie sprawdzanie bool ma = unikalne.Contains(2); // true – O(1)!
Queue<T> – kolejka FIFO
// Kolejka – pierwszy wchodzi, pierwszy wychodzi Queue<string> kolejka = new Queue<string>(); kolejka.Enqueue("Jan"); // Dodaj na koniec kolejka.Enqueue("Anna"); kolejka.Enqueue("Piotr"); string pierwszy = kolejka.Dequeue(); // "Jan" – zdejmij z początku string nastepny = kolejka.Peek(); // "Anna" – podejrzyj (bez usuwania)
Stack<T> – stos LIFO
// Stos – ostatni wchodzi, pierwszy wychodzi Stack<string> stos = new Stack<string>(); stos.Push("Pierwsza"); // Połóż na górę stos.Push("Druga"); stos.Push("Trzecia"); string gora = stos.Pop(); // "Trzecia" – zdejmij z góry string szczyt = stos.Peek(); // "Druga" – podejrzyj
Kiedy której używać?
| Potrzeba | Kolekcja | Przykład |
|---|---|---|
| Lista elementów o zmiennej długości | List<T> | Lista zakupów, uczniów |
| Szybkie szukanie po kluczu | Dictionary<K,V> | PESEL → Osoba, ID → Produkt |
| Zliczanie wystąpień | Dictionary<T,int> | Litera → ile razy |
| Bez duplikatów | HashSet<T> | Unikalne ID, tagi |
| Kolejność FIFO | Queue<T> | Kolejka do kasy, zadania |
| Kolejność LIFO | Stack<T> | Cofnij/Ponów, historia |
List<T> Contains/IndexOf | O(n) – wolne dla dużych list |
Dictionary / HashSet Contains | O(1) – błyskawiczne! |
Jeśli często sprawdzasz „czy zawiera?” – użyj Dictionary lub HashSet!
Częste błędy
❌ Błąd 1: Odwołanie do nieistniejącego klucza
❌ Źle – wyjątek!
var dict = new Dictionary<string, int>(); int x = dict["brak"]; // KeyNotFoundException!
✅ Dobrze
if (dict.TryGetValue("brak", out int x))
{
// użyj x
}
❌ Błąd 2: Add z duplikatem klucza
❌ Źle – wyjątek!
dict.Add("Jan", 5);
dict.Add("Jan", 6); // ArgumentException!
✅ Dobrze
dict["Jan"] = 5; dict["Jan"] = 6; // Nadpisuje – OK
❌ Błąd 3: Modyfikacja podczas iteracji
❌ Źle – wyjątek!
foreach (var item in lista)
{
if (item == "X")
lista.Remove(item); // CRASH!
}
✅ Dobrze
lista.RemoveAll(item => item == "X");
// lub: iteruj po kopii
foreach (var item in lista.ToList())
{
if (item == "X")
lista.Remove(item);
}
❌ Błąd 4: Zapomnienie using
❌ Źle – błąd kompilacji
// Brak using! List<int> lista = new List<int>(); // CS0246: The type 'List<>' could not be found
✅ Dobrze
using System.Collections.Generic; List<int> lista = new List<int>();
Podsumowanie
- List<T> – dynamiczna lista, zastępuje tablice
- Dictionary<K,V> – słownik klucz-wartość, szybkie wyszukiwanie
- HashSet<T> – zbiór unikalnych elementów
- Queue<T> – kolejka FIFO
- Stack<T> – stos LIFO
Schemat użycia
using System.Collections.Generic; // Lista List<string> lista = new List<string> { "A", "B" }; lista.Add("C"); lista.Remove("A"); bool ma = lista.Contains("B"); // Słownik Dictionary<string, int> dict = new Dictionary<string, int>(); dict["klucz"] = 123; if (dict.TryGetValue("klucz", out int val)) { } // Zliczanie foreach (var el in kolekcja) dict[el] = dict.GetValueOrDefault(el, 0) + 1;
Zadania praktyczne
📝 Zadanie 1: Lista zakupów
Napisz program z menu:
- 1 – Dodaj produkt
- 2 – Usuń produkt
- 3 – Wyświetl listę
- 4 – Sprawdź czy jest produkt
- 0 – Wyjście
📝 Zadanie 2: Książka telefoniczna
Utwórz Dictionary<string, string> (imię → telefon).
- Dodawanie kontaktu
- Wyszukiwanie numeru po imieniu
- Usuwanie kontaktu
- Wyświetlanie wszystkich
📝 Zadanie 3: Zliczanie liter
Pobierz tekst od użytkownika i wyświetl ile razy występuje każda litera (bez spacji, bez rozróżniania wielkości).
💡 Posortuj wyniki malejąco po liczbie wystąpień
⭐ Zadanie 4: Oceny uczniów
Utwórz Dictionary<string, List<int>> gdzie klucz to imię ucznia, a wartość to lista jego ocen.
- Dodawanie oceny dla ucznia
- Obliczanie średniej ucznia
- Wyświetlanie rankingu (po średniej)
⭐⭐ Zadanie 5: Anagram
Napisz metodę bool CzyAnagram(string a, string b) która sprawdza czy dwa słowa są anagramami (te same litery, inna kolejność).
💡 Użyj Dictionary do zliczenia liter w obu słowach i porównaj