Files
JR-priprava-na-skusku/priprava/rust_priprava4.md
2026-02-21 18:52:22 +01:00

28 KiB

Rust — HashMap a HashSet kompletný sprievodca


1. HashMap — čo to je

HashMap je kolekcia párov kľúč → hodnota. Každý kľúč je unikátny. Vyhľadávanie podľa kľúča je veľmi rýchle (O(1)).

Predstav si to ako slovník: slovo (kľúč) → definícia (hodnota).

use std::collections::HashMap;

Toto musíš importovať vždy. Nie je v prelude (na rozdiel od Vec alebo String).


2. HashMap — vytváranie

Prázdna mapa

// Musíš uviesť typy, alebo ich Rust odvodí z prvého insertu
let mut mapa: HashMap<String, i32> = HashMap::new();

// Alebo nechaj Rust odvodiť:
let mut mapa = HashMap::new();
mapa.insert("Anna".to_string(), 25);  // teraz Rust vie: HashMap<String, i32>

S kapacitou

Ak vieš dopredu koľko prvkov budeš mať, môžeš predalokovať:

let mut mapa: HashMap<String, i32> = HashMap::with_capacity(100);

Z iterátora (collect)

// Z vektora dvojíc
let dvojice = vec![("Anna", 25), ("Boris", 30), ("Cyril", 22)];
let mapa: HashMap<&str, i32> = dvojice.into_iter().collect();

// Z dvoch vektorov (zip)
let mena = vec!["Anna", "Boris", "Cyril"];
let veky = vec![25, 30, 22];
let mapa: HashMap<&str, i32> = mena.into_iter().zip(veky.into_iter()).collect();

3. HashMap — vkladanie (insert)

let mut mapa = HashMap::new();

// Základný insert — vráti Option<V> (starú hodnotu ak kľúč existoval)
let stara = mapa.insert("Anna".to_string(), 25);
// stara = None (kľúč neexistoval)

let stara = mapa.insert("Anna".to_string(), 26);
// stara = Some(25) (kľúč existoval, stará hodnota bola 25, teraz je 26)

Dôležité: insert vždy prepíše hodnotu ak kľúč existuje. Ak chceš vložiť len keď neexistuje, použi entry API (sekcia 7).


4. HashMap — čítanie

get — vráti Option<&V>

let mut mapa = HashMap::new();
mapa.insert("Anna".to_string(), 25);

// get berie referenciu na kľúč
let vek: Option<&i32> = mapa.get("Anna");     // Some(&25)
let vek: Option<&i32> = mapa.get("Boris");    // None

// Ak chceš hodnotu rovno:
if let Some(vek) = mapa.get("Anna") {
    println!("Anna má {} rokov", vek);
}

// Alebo s unwrap_or:
let vek = mapa.get("Anna").unwrap_or(&0);  // &25
let vek = mapa.get("Boris").unwrap_or(&0); // &0

get s &String vs &str

let mut mapa: HashMap<String, i32> = HashMap::new();
mapa.insert("Anna".to_string(), 25);

// Obe fungujú — Rust automaticky konvertuje &str na &String pri vyhľadávaní
mapa.get("Anna");                    // OK
mapa.get(&"Anna".to_string());       // OK, ale zbytočné

Hranatá zátvorka — panické čítanie

let vek = mapa["Anna"];  // 25 — ak kľúč neexistuje, PANIC!
// let vek = mapa["Boris"];  // PANIC! program spadne

Pravidlo: Na skúške vždy použi .get(), nikdy []. Nechceš panic.

get_mut — meniteľná referencia

let mut mapa = HashMap::new();
mapa.insert("Anna".to_string(), 25);

// Zmeň hodnotu na mieste
if let Some(vek) = mapa.get_mut("Anna") {
    *vek += 1;  // Anna má teraz 26
}

5. HashMap — kontrola existencie

let mapa: HashMap<String, i32> = /* ... */;

// contains_key — vráti bool
let existuje: bool = mapa.contains_key("Anna");

// Dĺžka
let pocet: usize = mapa.len();

// Prázdna?
let prazdna: bool = mapa.is_empty();

6. HashMap — mazanie

let mut mapa = HashMap::new();
mapa.insert("Anna".to_string(), 25);
mapa.insert("Boris".to_string(), 30);

// remove — vráti Option<V>
let odstraneny: Option<i32> = mapa.remove("Anna");
// Some(25) — Anna bola odstránená

let odstraneny: Option<i32> = mapa.remove("Cyril");
// None — Cyril neexistoval

// Vymaž všetko
mapa.clear();

7. HashMap — Entry API (NAJDÔLEŽITEJŠIE!)

Entry API je spôsob, ako efektívne pracovať s kľúčom, ktorý môže alebo nemusí existovať. Toto je na skúške skoro vždy.

or_insert — vlož ak neexistuje

let mut mapa: HashMap<String, i32> = HashMap::new();

// Ak "Anna" neexistuje, vlož 25. Ak existuje, nechaj.
// Vráti &mut na hodnotu (novú alebo existujúcu).
mapa.entry("Anna".to_string()).or_insert(25);
// mapa = {"Anna": 25}

mapa.entry("Anna".to_string()).or_insert(99);
// mapa = {"Anna": 25} — 99 sa NEVLOŽÍ, lebo Anna už existuje

Počítanie výskytov — najčastejší vzor

let slova = vec!["ahoj", "svet", "ahoj", "rust", "svet", "ahoj"];
let mut pocty: HashMap<String, usize> = HashMap::new();

for slovo in &slova {
    *pocty.entry(slovo.to_string()).or_insert(0) += 1;
}
// pocty = {"ahoj": 3, "svet": 2, "rust": 1}

Rozklad po krokoch:

1. pocty.entry("ahoj".to_string())
      → kľúč "ahoj" neexistuje → Vacant entry

2. .or_insert(0)
      → vlož 0 → vráť &mut 0

3. *(&mut 0) += 1
      → dereferencuj a zvýš → hodnota je teraz 1

Druhý prechod "ahoj":
1. pocty.entry("ahoj".to_string())
      → kľúč "ahoj" existuje → Occupied entry

2. .or_insert(0)
      → kľúč existuje, nevkladaj nič → vráť &mut 1

3. *(&mut 1) += 1
      → hodnota je teraz 2

or_insert_with — lazy vloženie

// or_insert vždy vyhodnotí argument (aj keď ho nevloží)
mapa.entry(kluc).or_insert(drahy_vypocet());  // drahy_vypocet() sa zavolá VŽDY

// or_insert_with zavolá closure LEN ak kľúč neexistuje
mapa.entry(kluc).or_insert_with(|| drahy_vypocet());  // zavolá sa len ak treba

or_default — použi Default trait

// Pre usize → 0, pre String → "", pre Vec → vec![], pre bool → false
let mut pocty: HashMap<String, usize> = HashMap::new();
*pocty.entry("ahoj".to_string()).or_default() += 1;
// or_default() je to isté ako or_insert(0) pre usize

// Zoskupovanie do vektorov:
let mut skupiny: HashMap<String, Vec<String>> = HashMap::new();
skupiny.entry("ovocie".to_string()).or_default().push("jablko".to_string());
skupiny.entry("ovocie".to_string()).or_default().push("hruška".to_string());
skupiny.entry("zelenina".to_string()).or_default().push("mrkva".to_string());
// skupiny = {"ovocie": ["jablko", "hruška"], "zelenina": ["mrkva"]}

and_modify — uprav existujúcu hodnotu

let mut mapa: HashMap<String, i32> = HashMap::new();

// Ak existuje, zvýš o 1. Ak neexistuje, vlož 1.
mapa.entry("Anna".to_string())
    .and_modify(|v| *v += 1)
    .or_insert(1);

Toto je alternatíva k vzoru *entry.or_insert(0) += 1, ale je explicitnejšie keď modifikácia a vloženie robia rôzne veci.


8. HashMap — iterácia

let mut mapa = HashMap::new();
mapa.insert("Anna".to_string(), 25);
mapa.insert("Boris".to_string(), 30);
mapa.insert("Cyril".to_string(), 22);

// Iterácia cez páry (kľúč, hodnota) — poradie NIE JE garantované!
for (meno, vek) in &mapa {
    println!("{}: {}", meno, vek);
}

// Len kľúče
for meno in mapa.keys() {
    println!("{}", meno);
}

// Len hodnoty
for vek in mapa.values() {
    println!("{}", vek);
}

// Meniteľná iterácia cez hodnoty
for vek in mapa.values_mut() {
    *vek += 1;  // všetkým pridaj rok
}

// Iterátor + collect — napr. kľúče do Vec
let mena: Vec<&String> = mapa.keys().collect();

// Iterátor + filter
let stari: Vec<(&String, &i32)> = mapa.iter().filter(|(_, vek)| **vek > 25).collect();

Dôležité: HashMap negarantuje poradie! Ak chceš zoradené, musíš po iterácii triediť alebo použiť BTreeMap.


9. HashMap — praktické vzory zo skúšky

Vzor 1: Počítanie podľa kategórie

struct Kniha { nazov: String, zaner: String }

fn pocty_zanrov(knihy: &[Kniha]) -> HashMap<String, usize> {
    let mut mapa = HashMap::new();
    for kniha in knihy {
        *mapa.entry(kniha.zaner.clone()).or_insert(0) += 1;
    }
    mapa
}

Vzor 2: Výpis štatistík

fn vypis_vydavatelstva_a_pocet(knihy: &[Kniha]) {
    let pocty = pocty_zanrov(knihy);
    for (zaner, pocet) in &pocty {
        println!("{}: {}", zaner, pocet);
    }
}

Vzor 3: Zoskupenie do vektorov

fn knihy_podla_zanru<'a>(knihy: &'a [Kniha]) -> HashMap<String, Vec<&'a Kniha>> {
    let mut mapa: HashMap<String, Vec<&Kniha>> = HashMap::new();
    for kniha in knihy {
        mapa.entry(kniha.zaner.clone()).or_default().push(kniha);
    }
    mapa
}

Vzor 4: Najdi najčastejší

fn najcastejsi_zaner(knihy: &[Kniha]) -> Option<String> {
    let pocty = pocty_zanrov(knihy);
    pocty.into_iter()
        .max_by_key(|(_, pocet)| *pocet)
        .map(|(zaner, _)| zaner)
}

10. HashMap — Úloha na precvičenie

Úloha HM-1: Napíš funkciu pocitaj_slova(text: &str) -> HashMap<String, usize> ktorá rozdelí text na slová (.split_whitespace()) a spočíta výskyt každého slova. Preveď slová na lowercase (.to_lowercase()).

Úloha HM-2: Máš:

struct Objednavka { zakaznik: String, suma: f64, mesto: String }

Napíš:

  • a) pocet_objednavok_podla_mesta(objednavky: &[Objednavka]) -> HashMap<String, usize>
  • b) celkova_suma_podla_zakaznika(objednavky: &[Objednavka]) -> HashMap<String, f64>
  • c) objednavky_podla_mesta(objednavky: &[Objednavka]) -> HashMap<String, Vec<&Objednavka>>
  • d) mesto_s_najviac_objednavkami(objednavky: &[Objednavka]) -> Option<String>
  • e) najlepsi_zakaznik(objednavky: &[Objednavka]) -> Option<String> — zákazník s najvyššou celkovou sumou

Úloha HM-3: Napíš funkciu invertuj(mapa: &HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>>. Vstup: kategórie → položky. Výstup: položka → kategórie. Príklad:

Vstup:  {"ovocie": ["jablko", "hruška"], "červené": ["jablko", "jahoda"]}
Výstup: {"jablko": ["ovocie", "červené"], "hruška": ["ovocie"], "jahoda": ["červené"]}

Úloha HM-4: Napíš funkciu najcastejsi_znak(text: &str) -> Option<char> — vráti najčastejší znak v texte (ignoruj medzery). Ak je text prázdny, vráť None.



11. HashSet — čo to je

HashSet je kolekcia unikátnych hodnôt. Žiadna hodnota sa nemôže opakovať. Je to vlastne HashMap kde ťa zaujímajú len kľúče, nie hodnoty.

Predstav si to ako množinu v matematike: {1, 2, 3}.

use std::collections::HashSet;

12. HashSet — vytváranie

Prázdna množina

let mut mnozina: HashSet<String> = HashSet::new();

// Alebo nechaj odvodiť:
let mut mnozina = HashSet::new();
mnozina.insert("Anna".to_string());  // teraz Rust vie: HashSet<String>

Z iterátora (collect)

let cisla = vec![1, 2, 3, 2, 1, 4, 3, 5];
let unikatne: HashSet<i32> = cisla.into_iter().collect();
// {1, 2, 3, 4, 5} — duplikáty zmizli

// Z reťazca — množina znakov
let znaky: HashSet<char> = "abrakadabra".chars().collect();
// {'a', 'b', 'r', 'k', 'd'}

S kapacitou

let mut mnozina: HashSet<i32> = HashSet::with_capacity(100);

13. HashSet — vkladanie (insert)

let mut mnozina = HashSet::new();

// insert vráti bool — true ak sa vložilo (prvok neexistoval), false ak už existoval
let nova: bool = mnozina.insert("Anna".to_string());
// true — Anna bola pridaná

let nova: bool = mnozina.insert("Anna".to_string());
// false — Anna už existuje, nič sa nezmenilo

Toto je užitočné na kontrolu duplikátov:

if !mnozina.insert(hodnota) {
    println!("Duplikát!");
}

14. HashSet — kontrola existencie (contains)

let mut mnozina = HashSet::new();
mnozina.insert("Anna".to_string());
mnozina.insert("Boris".to_string());

let existuje: bool = mnozina.contains("Anna");   // true
let existuje: bool = mnozina.contains("Cyril");  // false

// Dĺžka
let pocet: usize = mnozina.len();   // 2

// Prázdna?
let prazdna: bool = mnozina.is_empty();  // false

contains s &str vs &String: Rovnako ako HashMap — contains("Anna") funguje aj keď máš HashSet<String>.


15. HashSet — mazanie

let mut mnozina = HashSet::new();
mnozina.insert(1);
mnozina.insert(2);
mnozina.insert(3);

// remove — vráti bool (true ak existoval)
let bol_tam: bool = mnozina.remove(&2);  // true, mnozina = {1, 3}
let bol_tam: bool = mnozina.remove(&5);  // false, nič sa nezmenilo

// Vymaž všetko
mnozina.clear();

16. HashSet — iterácia

let mnozina: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();

// Iterácia — poradie NIE JE garantované!
for prvok in &mnozina {
    println!("{}", prvok);
}

// Iterátor + filter + collect
let parne: HashSet<&i32> = mnozina.iter().filter(|c| *c % 2 == 0).collect();

// Do Vec
let vektor: Vec<&i32> = mnozina.iter().collect();

// Počet prvkov spĺňajúcich podmienku
let pocet_parnych: usize = mnozina.iter().filter(|c| *c % 2 == 0).count();

17. HashSet — množinové operácie

Toto je to, čo robí HashSet špeciálnym oproti Vec. Tieto operácie sú veľmi efektívne.

Prienik (intersection) — prvky v oboch

let a: HashSet<i32> = vec![1, 2, 3, 4].into_iter().collect();
let b: HashSet<i32> = vec![3, 4, 5, 6].into_iter().collect();

// intersection vráti iterátor
let prienik: HashSet<&i32> = a.intersection(&b).collect();
// {&3, &4}

// Alebo do Vec
let prienik: Vec<&i32> = a.intersection(&b).collect();

Zjednotenie (union) — prvky v aspoň jednom

let zjednotenie: HashSet<&i32> = a.union(&b).collect();
// {&1, &2, &3, &4, &5, &6}

Rozdiel (difference) — prvky v A, ktoré nie sú v B

let rozdiel: HashSet<&i32> = a.difference(&b).collect();
// {&1, &2}

// Opačný smer:
let rozdiel_opacny: HashSet<&i32> = b.difference(&a).collect();
// {&5, &6}

Symetrický rozdiel — prvky v jednom, ale nie v oboch

let sym_rozdiel: HashSet<&i32> = a.symmetric_difference(&b).collect();
// {&1, &2, &5, &6}

Podmnožina a nadmnožina

let mala: HashSet<i32> = vec![1, 2].into_iter().collect();
let velka: HashSet<i32> = vec![1, 2, 3, 4].into_iter().collect();

let je_podmnozina: bool = mala.is_subset(&velka);     // true
let je_nadmnozina: bool = velka.is_superset(&mala);    // true
let je_disjunktna: bool = mala.is_disjoint(&velka);    // false (majú spoločné prvky)

Operátory

// Tieto operátory fungujú tiež:
let prienik = &a & &b;           // intersection
let zjednotenie = &a | &b;      // union
let rozdiel = &a - &b;          // difference
let sym_rozdiel = &a ^ &b;      // symmetric_difference
// Vracajú nový HashSet (nie referencie)

18. HashSet — praktické použitie v Ruste

Vzor 1: Sledovanie už videných hodnôt

fn ma_duplikaty(cisla: &[i32]) -> bool {
    let mut videne = HashSet::new();
    for c in cisla {
        if !videne.insert(c) {
            return true;  // insert vrátil false → duplikát
        }
    }
    false
}

Vzor 2: Unikátne hodnoty z kolekcie

fn unikatne_zanre(knihy: &[Kniha]) -> Vec<String> {
    let mnozina: HashSet<String> = knihy.iter().map(|k| k.zaner.clone()).collect();
    mnozina.into_iter().collect()
}

Vzor 3: Rýchle vyhľadávanie (namiesto Vec.contains)

// Vec.contains je pomalé (O(n)) — prechádza celý vektor
let povolene_vec = vec!["admin", "editor", "viewer"];
povolene_vec.contains(&"admin"); // O(n)

// HashSet.contains je rýchle (O(1))
let povolene_set: HashSet<&str> = vec!["admin", "editor", "viewer"].into_iter().collect();
povolene_set.contains("admin"); // O(1)

Ak kontroluješ existenciu opakovane (napr. v cykle), vždy použi HashSet.

Vzor 4: Obesenec — uhádnuté písmená (zo skúšky!)

struct Hra {
    hladane_slovo: String,
    uhadnute_pismena: HashSet<char>,
    skusane_pismena: HashSet<char>,
    pocet_zivotov: u8,
}

impl Hra {
    fn new(slovo: &str) -> Hra {
        Hra {
            hladane_slovo: slovo.to_lowercase(),
            uhadnute_pismena: HashSet::new(),
            skusane_pismena: HashSet::new(),
            pocet_zivotov: 6,
        }
    }

    fn tipni(&mut self, pismeno: char) {
        let p = pismeno.to_lowercase().next().unwrap();

        // Ak sme už skúšali toto písmeno — preskočíme
        if !self.skusane_pismena.insert(p) {
            println!("Toto písmeno si už skúšal!");
            return;
        }

        // Je písmeno v slove?
        if self.hladane_slovo.contains(p) {
            self.uhadnute_pismena.insert(p);
            println!("Správne!");
        } else {
            self.pocet_zivotov -= 1;
            println!("Zle! Zostáva životov: {}", self.pocet_zivotov);
        }
    }

    fn zobraz_slovo(&self) -> String {
        self.hladane_slovo.chars().map(|c| {
            if self.uhadnute_pismena.contains(&c) { c } else { '_' }
        }).collect()
    }

    fn je_vyhra(&self) -> bool {
        self.hladane_slovo.chars().all(|c| self.uhadnute_pismena.contains(&c))
    }
}

19. HashSet — kedy HashSet, kedy Vec

Situácia Použi
Potrebuješ poradie prvkov Vec
Potrebuješ duplikáty Vec
Potrebuješ index (prvý, druhý, ...) Vec
Často kontroluješ contains HashSet
Potrebuješ unikátne hodnoty HashSet
Robíš množinové operácie (prienik, ...) HashSet
Sleduješ "už som videl" HashSet
Chceš rýchle pridávanie + kontrolu HashSet

20. HashMap vs HashSet

HashMap HashSet
Ukladá kľúč → hodnota len hodnota
Import use std::collections::HashMap use std::collections::HashSet
Vloženie .insert(kľúč, hodnota)Option<V> .insert(hodnota)bool
Vyhľadanie .get(&kľúč)Option<&V> .contains(&hodnota)bool
Mazanie .remove(&kľúč)Option<V> .remove(&hodnota)bool
Entry API áno nie (nepotrebné)
Množinové operácie nie áno (intersection, union, ...)

HashSet je vnútorne implementovaný ako HashMap<T, ()> — teda HashMap kde hodnota je prázdna.


21. HashSet — Úloha na precvičenie

Úloha HS-1: Napíš funkciu unikatne_znaky(text: &str) -> HashSet<char> — vráti množinu všetkých unikátnych znakov v texte (bez medzier).

Úloha HS-2: Napíš funkciu spolocne_znaky(text1: &str, text2: &str) -> HashSet<char> — vráti znaky, ktoré sa vyskytujú v oboch textoch.

Úloha HS-3: Napíš funkciu ma_duplikaty(cisla: &[i32]) -> bool — vráti true ak vektor obsahuje duplikáty. Použi HashSet.

Úloha HS-4: Napíš funkciu unikatne_slova(text: &str) -> usize — vráti počet unikátnych slov v texte (case-insensitive, použi .to_lowercase()).

Úloha HS-5: Máš dva vektory mien. Napíš:

  • a) spolocni(a: &[String], b: &[String]) -> Vec<String> — mená v oboch
  • b) len_v_prvom(a: &[String], b: &[String]) -> Vec<String> — mená len v prvom

Úloha HS-6: Implementuj štruktúru Slovnik pre obesenca:

struct Slovnik {
    slova: HashMap<String, Vec<String>>,  // kategória → slová
}

Napíš:

  • a) new() -> Slovnik
  • b) pridaj_kategoriu(&mut self, kategoria: &str) — pridá prázdnu kategóriu ak neexistuje
  • c) pridaj_slovo(&mut self, kategoria: &str, slovo: &str) -> Result<(), ()> — Err ak kategória neexistuje
  • d) nahodne_slovo(&self, kategoria: &str) -> Option<&String> — náhodné slovo z kategórie (použi rand)
  • e) vsetky_kategorie(&self) -> Vec<&String> — zoznam kategórií
  • f) pocet_slov_v_kategorii(&self, kategoria: &str) -> Option<usize> — None ak kategória neexistuje

22. Kombinovaná úloha — HashMap + HashSet spolu

Úloha K-1: Máš:

struct Student {
    meno: String,
    predmety: HashSet<String>,
    znamky: HashMap<String, u8>,  // predmet → známka
}

Napíš:

  • a) fn pridaj_predmet(&mut self, predmet: &str) — pridá predmet do množiny
  • b) fn zadaj_znamku(&mut self, predmet: &str, znamka: u8) -> Result<(), ()> — Err ak študent nemá predmet
  • c) fn priemer(&self) -> Option<f64> — priemer známok, None ak žiadne
  • d) fn spolocne_predmety(s1: &Student, s2: &Student) -> HashSet<String> — predmety oboch
  • e) fn ma_vsetky_znamky(&self) -> bool — má známku z každého predmetu?

Úloha K-2: Máš:

struct Skola {
    studenti: Vec<Student>,
}

Napíš:

  • a) fn vsetky_predmety(&self) -> HashSet<String> — všetky predmety naprieč všetkými študentmi
  • b) fn studenti_s_predmetom(&self, predmet: &str) -> Vec<&Student> — študenti zapísaní na predmet
  • c) fn priemer_za_predmet(&self, predmet: &str) -> Option<f64> — priemer známok za predmet naprieč študentmi
  • d) fn najlepsi_student(&self) -> Option<&Student> — študent s najnižším priemerom (kto má znamky)



RIEŠENIA

HM-1

fn pocitaj_slova(text: &str) -> HashMap<String, usize> {
    let mut mapa = HashMap::new();
    for slovo in text.split_whitespace() {
        *mapa.entry(slovo.to_lowercase()).or_insert(0) += 1;
    }
    mapa
}

HM-2

struct Objednavka { zakaznik: String, suma: f64, mesto: String }

// a)
fn pocet_objednavok_podla_mesta(objednavky: &[Objednavka]) -> HashMap<String, usize> {
    let mut mapa = HashMap::new();
    for obj in objednavky {
        *mapa.entry(obj.mesto.clone()).or_insert(0) += 1;
    }
    mapa
}

// b)
fn celkova_suma_podla_zakaznika(objednavky: &[Objednavka]) -> HashMap<String, f64> {
    let mut mapa: HashMap<String, f64> = HashMap::new();
    for obj in objednavky {
        *mapa.entry(obj.zakaznik.clone()).or_insert(0.0) += obj.suma;
    }
    mapa
}

// c)
fn objednavky_podla_mesta<'a>(objednavky: &'a [Objednavka]) -> HashMap<String, Vec<&'a Objednavka>> {
    let mut mapa: HashMap<String, Vec<&Objednavka>> = HashMap::new();
    for obj in objednavky {
        mapa.entry(obj.mesto.clone()).or_default().push(obj);
    }
    mapa
}

// d)
fn mesto_s_najviac_objednavkami(objednavky: &[Objednavka]) -> Option<String> {
    let pocty = pocet_objednavok_podla_mesta(objednavky);
    pocty.into_iter()
        .max_by_key(|(_, pocet)| *pocet)
        .map(|(mesto, _)| mesto)
}

// e)
fn najlepsi_zakaznik(objednavky: &[Objednavka]) -> Option<String> {
    let sumy = celkova_suma_podla_zakaznika(objednavky);
    sumy.into_iter()
        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
        .map(|(zakaznik, _)| zakaznik)
}

HM-3

fn invertuj(mapa: &HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
    let mut nova: HashMap<String, Vec<String>> = HashMap::new();
    for (kategoria, polozky) in mapa {
        for polozka in polozky {
            nova.entry(polozka.clone()).or_default().push(kategoria.clone());
        }
    }
    nova
}

HM-4

fn najcastejsi_znak(text: &str) -> Option<char> {
    let mut pocty: HashMap<char, usize> = HashMap::new();
    for c in text.chars() {
        if c != ' ' {
            *pocty.entry(c).or_insert(0) += 1;
        }
    }
    pocty.into_iter()
        .max_by_key(|(_, pocet)| *pocet)
        .map(|(znak, _)| znak)
}

HS-1

fn unikatne_znaky(text: &str) -> HashSet<char> {
    text.chars().filter(|c| *c != ' ').collect()
}

HS-2

fn spolocne_znaky(text1: &str, text2: &str) -> HashSet<char> {
    let a: HashSet<char> = text1.chars().collect();
    let b: HashSet<char> = text2.chars().collect();
    a.intersection(&b).copied().collect()
}

HS-3

fn ma_duplikaty(cisla: &[i32]) -> bool {
    let mut videne = HashSet::new();
    for c in cisla {
        if !videne.insert(c) {
            return true;
        }
    }
    false
}

// Alebo kratšie (ale menej efektívne — prejde celý vektor):
fn ma_duplikaty(cisla: &[i32]) -> bool {
    let mnozina: HashSet<&i32> = cisla.iter().collect();
    mnozina.len() != cisla.len()
}

HS-4

fn unikatne_slova(text: &str) -> usize {
    let mnozina: HashSet<String> = text.split_whitespace()
        .map(|s| s.to_lowercase())
        .collect();
    mnozina.len()
}

HS-5

// a)
fn spolocni(a: &[String], b: &[String]) -> Vec<String> {
    let set_a: HashSet<&String> = a.iter().collect();
    let set_b: HashSet<&String> = b.iter().collect();
    set_a.intersection(&set_b).map(|s| (*s).clone()).collect()
}

// b)
fn len_v_prvom(a: &[String], b: &[String]) -> Vec<String> {
    let set_a: HashSet<&String> = a.iter().collect();
    let set_b: HashSet<&String> = b.iter().collect();
    set_a.difference(&set_b).map(|s| (*s).clone()).collect()
}

HS-6

use rand::seq::SliceRandom;

struct Slovnik {
    slova: HashMap<String, Vec<String>>,
}

impl Slovnik {
    // a)
    fn new() -> Slovnik {
        Slovnik { slova: HashMap::new() }
    }

    // b)
    fn pridaj_kategoriu(&mut self, kategoria: &str) {
        self.slova.entry(kategoria.to_string()).or_default();
    }

    // c)
    fn pridaj_slovo(&mut self, kategoria: &str, slovo: &str) -> Result<(), ()> {
        let kat = self.slova.get_mut(kategoria).ok_or(())?;
        kat.push(slovo.to_string());
        Ok(())
    }

    // d)
    fn nahodne_slovo(&self, kategoria: &str) -> Option<&String> {
        let kat = self.slova.get(kategoria)?;
        let mut rng = rand::thread_rng();
        kat.choose(&mut rng)
    }

    // e)
    fn vsetky_kategorie(&self) -> Vec<&String> {
        self.slova.keys().collect()
    }

    // f)
    fn pocet_slov_v_kategorii(&self, kategoria: &str) -> Option<usize> {
        self.slova.get(kategoria).map(|v| v.len())
    }
}

K-1

struct Student {
    meno: String,
    predmety: HashSet<String>,
    znamky: HashMap<String, u8>,
}

impl Student {
    // a)
    fn pridaj_predmet(&mut self, predmet: &str) {
        self.predmety.insert(predmet.to_string());
    }

    // b)
    fn zadaj_znamku(&mut self, predmet: &str, znamka: u8) -> Result<(), ()> {
        if !self.predmety.contains(predmet) {
            return Err(());
        }
        self.znamky.insert(predmet.to_string(), znamka);
        Ok(())
    }

    // c)
    fn priemer(&self) -> Option<f64> {
        if self.znamky.is_empty() {
            return None;
        }
        let sucet: u32 = self.znamky.values().map(|z| *z as u32).sum();
        Some(sucet as f64 / self.znamky.len() as f64)
    }

    // d)
    fn spolocne_predmety(s1: &Student, s2: &Student) -> HashSet<String> {
        s1.predmety.intersection(&s2.predmety).cloned().collect()
    }

    // e)
    fn ma_vsetky_znamky(&self) -> bool {
        self.predmety.iter().all(|p| self.znamky.contains_key(p))
    }
}

K-2

struct Skola {
    studenti: Vec<Student>,
}

impl Skola {
    // a)
    fn vsetky_predmety(&self) -> HashSet<String> {
        let mut vsetky = HashSet::new();
        for student in &self.studenti {
            for predmet in &student.predmety {
                vsetky.insert(predmet.clone());
            }
        }
        vsetky
    }

    // Alternatíva s iterátormi:
    // fn vsetky_predmety(&self) -> HashSet<String> {
    //     self.studenti.iter()
    //         .flat_map(|s| s.predmety.iter().cloned())
    //         .collect()
    // }

    // b)
    fn studenti_s_predmetom(&self, predmet: &str) -> Vec<&Student> {
        self.studenti.iter()
            .filter(|s| s.predmety.contains(predmet))
            .collect()
    }

    // c)
    fn priemer_za_predmet(&self, predmet: &str) -> Option<f64> {
        let znamky: Vec<u8> = self.studenti.iter()
            .filter_map(|s| s.znamky.get(predmet).copied())
            .collect();
        if znamky.is_empty() {
            return None;
        }
        let sucet: u32 = znamky.iter().map(|z| *z as u32).sum();
        Some(sucet as f64 / znamky.len() as f64)
    }

    // d)
    fn najlepsi_student(&self) -> Option<&Student> {
        self.studenti.iter()
            .filter(|s| !s.znamky.is_empty())
            .min_by(|a, b| {
                let priemer_a = a.priemer().unwrap();
                let priemer_b = b.priemer().unwrap();
                priemer_a.partial_cmp(&priemer_b).unwrap()
            })
    }
}