Kolekcije u C#: List, Dictionary, HashSet, Queue i Stack

Lekcija 9 od 15 · C#

Niz ima nepromenljivu dužinu. Kolekcije iz prostora imena System.Collections.Generic rastu i smanjuju se po potrebi i imaju gotove metode za dodavanje, brisanje i pretragu. Tip elemenata piše se u uglastim zagradama: List<int> je lista celih brojeva, Dictionary<string, int> rečnik koji tekst preslikava u broj.

Pre nego što počneš: potrebno predznanje – klase i objekti, nizovi.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su konzolne aplikacije u C#-u (.NET 8 ili noviji, bez „top-level statements“); svaki je proveren i daje navedeni izlaz.

List<T>: niz promenljive dužine

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> drugari = new List<string> { "Ana", "Marko" };
        drugari.Add("Iva");                    // dodavanje na kraj
        drugari.Insert(1, "Luka");             // ubacivanje na poziciju 1
        Console.WriteLine(string.Join(", ", drugari));
        Console.WriteLine($"Broj: {drugari.Count}");

        drugari.Remove("Marko");               // brisanje po vrednosti
        drugari.Sort();
        Console.WriteLine(string.Join(", ", drugari));
        Console.WriteLine(drugari.Contains("Iva"));
        Console.WriteLine(drugari.IndexOf("Luka"));

        for (int i = 0; i < drugari.Count; i++)  // pristup po indeksu, kao kod niza
            Console.WriteLine($"{i + 1}. {drugari[i]}");
    }
}

Broj elemenata liste daje svojstvo Count (kod niza je to Length). Kada broj podataka nije unapred poznat, lista je bolji izbor od niza:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var brojevi = new List<int>();
        while (true)                               // učitavanje do nule
        {
            int x = int.Parse(Console.ReadLine());
            if (x == 0) break;
            brojevi.Add(x);
        }
        brojevi.RemoveAll(b => b < 0);             // ukloni sve negativne
        brojevi.Sort((a, b) => b.CompareTo(a));    // sortiraj opadajuće
        Console.WriteLine(string.Join(" ", brojevi));
    }
}

Izrazi b => b < 0 i (a, b) => b.CompareTo(a) su lambda izrazi – kratke funkcije napisane na mestu upotrebe. Detaljno su objašnjeni u lekciji o LINQ-u.

Dictionary<K, V>: rečnik (ključ → vrednost)

Rečnik čuva parove ključ – vrednost i brzo pronalazi vrednost po ključu. Ključevi su jedinstveni.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var broj = new Dictionary<string, int>();
        foreach (string rec in Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries))
        {
            if (broj.ContainsKey(rec))
                broj[rec]++;                       // reč već postoji – uvećaj brojač
            else
                broj[rec] = 1;                     // nova reč
        }
        foreach (var par in broj)                  // par je KeyValuePair<string, int>
            Console.WriteLine($"{par.Key}: {par.Value}");

        if (broj.TryGetValue("mis", out int m))
            Console.WriteLine($"mis: {m}");
        else
            Console.WriteLine("mis se ne pojavljuje");
    }
}

Čitanje ključa koji ne postoji (broj["mis"]) prekida program greškom, zato se koristi ContainsKey ili TryGetValue. Redosled obilaska rečnika nije zagarantovan; kada su potrebni sortirani ključevi, koristi se SortedDictionary.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var imenik = new Dictionary<string, string>
        {
            ["Ana"] = "064 111 222",
            ["Marko"] = "063 333 444"
        };
        imenik["Iva"] = "065 555 666";         // dodavanje
        imenik["Ana"] = "064 999 888";         // izmena postojećeg ključa
        imenik.Remove("Marko");                // brisanje

        foreach (string ime in imenik.Keys)
            Console.WriteLine($"{ime}: {imenik[ime]}");
        Console.WriteLine($"Ukupno kontakata: {imenik.Count}");
    }
}

HashSet<T>: skup bez ponavljanja

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var a = new HashSet<int> { 1, 2, 3, 4, 5 };
        var b = new HashSet<int> { 4, 5, 6, 7 };
        Console.WriteLine(a.Add(3));               // False – 3 već postoji
        Console.WriteLine(a.Contains(4));          // brza provera pripadnosti

        var presek = new HashSet<int>(a);
        presek.IntersectWith(b);
        var unija = new HashSet<int>(a);
        unija.UnionWith(b);
        Console.WriteLine("Presek: " + string.Join(" ", presek));
        Console.WriteLine("Unija: " + string.Join(" ", unija));
    }
}

Skup je najbolji kada treba proveriti „da li se već pojavilo“ ili ukloniti duplikate: new HashSet<int>(niz).

Queue<T> (red) i Stack<T> (stek)

Red radi po pravilu „prvi došao, prvi uslužen“ (FIFO), a stek po pravilu „poslednji stavljen, prvi uzet“ (LIFO).

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var red = new Queue<string>();
        red.Enqueue("Ana");                    // stani u red
        red.Enqueue("Bojan");
        red.Enqueue("Ceca");
        Console.WriteLine($"Na redu je: {red.Peek()}");
        while (red.Count > 0)
            Console.Write(red.Dequeue() + " ");  // izađi iz reda
        Console.WriteLine();

        var stek = new Stack<int>();
        for (int i = 1; i <= 4; i++)
            stek.Push(i * 10);                 // stavi na vrh
        Console.WriteLine($"Vrh steka: {stek.Peek()}");
        while (stek.Count > 0)
            Console.Write(stek.Pop() + " ");     // skini sa vrha
        Console.WriteLine();
    }
}
KolekcijaKada je koristitiVažne metode
List<T>niz promenljive dužineAdd, Insert, Remove, Sort, Contains, Count
Dictionary<K,V>brza pretraga po ključu, brojanje[ključ], ContainsKey, TryGetValue, Remove
HashSet<T>jedinstveni elementi, pripadnostAdd, Contains, UnionWith, IntersectWith
Queue<T>obrada redom dolaskaEnqueue, Dequeue, Peek
Stack<T>poslednji ulazi – prvi izlaziPush, Pop, Peek

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: lako

Najčešća reč

Učitati red reči razdvojenih razmakom i ispisati reč koja se najčešće pojavljuje i koliko puta. Ako ima više takvih reči, ispisati onu koja je prva po abecedi.

Primer 1

Ulaz

da ne da mozda ne da

Izlaz

da 3

Primer 2

Ulaz

b a b a

Izlaz

a 2
ZADATAK 02Težina: srednje

Zagrade

Učitati red koji sadrži zagrade ( ) [ ] { } (i eventualno druge znakove) i ispisati DA ako su zagrade pravilno uparene i ugnježdene, a NE u suprotnom.

Primer 1

Ulaz

{[()()]}

Izlaz

DA

Primer 2

Ulaz

([)]

Izlaz

NE

Primer 3

Ulaz

((

Izlaz

NE
Česte greške i saveti
  • Na vrh fajla dodaj using System.Collections.Generic;.
  • Lista ima Count, a niz Length.
  • Pre čitanja iz rečnika proveri ključ (ContainsKey ili TryGetValue).
  • Kolekcija se ne sme menjati (dodavati ili brisati elemente) dok se kroz nju prolazi petljom foreach.

Proveri znanje

Otvori kviz

Kolekcije u C#

1. Koje svojstvo daje broj elemenata liste?




2. Koja kolekcija je najbolja za brojanje koliko puta se pojavila svaka reč?




3. Šta vraća Add na HashSet ako element već postoji?




4. Koja kolekcija radi po pravilu „poslednji ulazi, prvi izlazi“?