Files
JR-priprava-na-skusku/priprava/rust_priprava6.md
2026-02-26 18:59:33 +01:00

13 KiB

HashSet — krok za krokom

Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej.

use std::collections::HashSet; // toto treba vždy

Kapitola 1: Čo to je a vytváranie

HashSet je kolekcia unikátnych hodnôt. Žiadna hodnota sa neopakuje. Negarantuje poradie.

Rozdiel oproti Vec: Vec môže mať duplikáty a pamätá poradie. Rozdiel oproti HashMap: HashMap má kľúč → hodnota. HashSet má len hodnotu (žiadny pár).

// Prázdny set
let mut mnozina: HashSet<i32> = HashSet::new();

// Alebo nechaj Rust odvodiť:
let mut mnozina = HashSet::new();
mnozina.insert(1);  // teraz Rust vie: HashSet<i32>

// Z vektora — duplikáty zmiznú!
let cisla = vec![1, 2, 3, 2, 1, 4, 3, 5];
let unikatne: HashSet<i32> = cisla.into_iter().collect();
// {1, 2, 3, 4, 5} — len 5 prvkov, nie 8

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

Úlohy 1

1a. Vytvor HashSet čísel z vektora [5, 3, 5, 1, 3, 2, 1, 4]. Vypíš koľko prvkov má. Vypíš prvky.

1b. Vytvor HashSet znakov z reťazca "hello world". Koľko unikátnych znakov to má? (vrátane medzery)

1c. Čo vypíše? Tipni, potom over:

let v = vec![10, 20, 10, 30, 20, 10];
let s: HashSet<i32> = v.into_iter().collect();
println!("{}", s.len());

Kapitola 2: insert — vráti bool!

Toto je kľúčový rozdiel oproti HashMap. insert vráti bool:

  • true → prvok bol nový, vložil sa
  • false → prvok už existoval, nič sa nezmenilo
let mut mnozina = HashSet::new();

let novy = mnozina.insert("Anna".to_string());
// novy = true — Anna bola pridaná

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

Praktické — detekcia duplikátov:

let mut videne = HashSet::new();
let slova = vec!["ahoj", "svet", "ahoj", "rust"];

for slovo in &slova {
    if !videne.insert(*slovo) {
        println!("Duplikát: {}", slovo);
    }
}
// Vypíše: Duplikát: ahoj

Logika: insert vráti false → prvok tam už bol → je to duplikát.

Úlohy 2

2a. Napíš funkciu ma_duplikaty(cisla: &[i32]) -> bool. Použi HashSet a insert. Vráť true hneď ako nájdeš prvý duplikát.

2b. Napíš funkciu najdi_duplikaty(cisla: &[i32]) -> Vec<i32> — vráti vektor všetkých duplikovaných hodnôt (každý duplikát len raz). Hint: použi dva HashSety — videne a duplikaty.

2c. Čo vypíše? Tipni:

let mut s = HashSet::new();
println!("{}", s.insert(1));
println!("{}", s.insert(2));
println!("{}", s.insert(1));
println!("{}", s.insert(3));
println!("{}", s.insert(2));

Kapitola 3: contains, remove, len

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

// contains — je tam?
mnozina.contains("Anna");   // true
mnozina.contains("Cyril");  // false

// len — počet prvkov
mnozina.len();       // 2

// is_empty
mnozina.is_empty();  // false

// remove — vráti bool (true ak existoval a bol odstránený)
mnozina.remove("Anna");   // true — Anna odstránená
mnozina.remove("Cyril");  // false — Cyril tam ani nebol

// clear — vymaž všetko
mnozina.clear();

Dôležité: contains na HashSet je O(1) — veľmi rýchle. Na Vec je O(n) — pomalé. Ak často kontroluješ existenciu, použi HashSet.

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

// RÝCHLE — HashSet.contains
let povolene: HashSet<&str> = vec!["admin", "editor", "viewer"].into_iter().collect();
povolene.contains("admin"); // O(1)

Úlohy 3

3a. Vytvor HashSet povolených farieb: "červená", "modrá", "zelená". Napíš funkciu:

fn je_povolena(povolene: &HashSet<String>, farba: &str) -> bool

3b. Napíš funkciu pocet_unikatnych(cisla: &[i32]) -> usize — koľko rôznych hodnôt je vo vektore.

3c. Máš HashSet mien. Odstráň všetky mená kratšie ako 4 znaky. Použi .retain():

// retain nechá len prvky, kde closure vráti true
mnozina.retain(|meno| meno.len() >= 4);

Kapitola 4: Iterácia

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

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

// filter + collect — vyfiltruj do nového HashSetu
let parne: HashSet<&i32> = mnozina.iter().filter(|c| *c % 2 == 0).collect();

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

// any — existuje prvok splňajúci podmienku?
let ma_velke: bool = mnozina.iter().any(|c| *c > 3);

// count s filtrom
let pocet_parnych: usize = mnozina.iter().filter(|c| *c % 2 == 0).count();

// sum
let sucet: i32 = mnozina.iter().sum();

Úlohy 4

4a. Máš HashSet mien (String). Vypíš len mená začínajúce na 'A'.

4b. Napíš funkciu sucet(mnozina: &HashSet<i32>) -> i32 — súčet všetkých prvkov.

4c. Napíš funkciu najdlhsi(mnozina: &HashSet<String>) -> Option<&String> — najdlhší reťazec. Použi .max_by_key().

4d. Máš HashSet čísel. Koľko z nich je deliteľných 3? Napíš to jedným výrazom.


Kapitola 5: Prienik (intersection)

Prienik = prvky, ktoré sú v oboch množinách.

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 cez &i32
let prienik: HashSet<&i32> = a.intersection(&b).collect();
// {&3, &4}

// Ak chceš vlastné hodnoty (nie referencie), použi .copied()
let prienik: HashSet<i32> = a.intersection(&b).copied().collect();
// {3, 4}

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

Skratka operátorom:

let prienik: HashSet<i32> = &a & &b;
// {3, 4} — rovnaký výsledok, kratší zápis

Úlohy 5

5a. Máš dva zoznamy mien (Vec). Preveď ich na HashSety a nájdi spoločné mená.

let trieda_a = vec!["Anna", "Boris", "Cyril", "Dana"];
let trieda_b = vec!["Boris", "Dana", "Emil", "Fero"];
// Spoločné: Boris, Dana

5b. Napíš funkciu:

fn spolocne_pismena(slovo1: &str, slovo2: &str) -> HashSet<char>

Vráti písmená, ktoré sú v oboch slovách.

5c. Máš 3 HashSety. Nájdi prvky, ktoré sú vo všetkých troch. Hint: najprv prienik A∩B, potom výsledok ∩ C.


Kapitola 6: Zjednotenie (union)

Zjednotenie = prvky, ktoré sú v aspoň jednej z množín. Duplikáty sa nezopakujú.

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

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

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

Skratka:

let zjednotenie: HashSet<i32> = &a | &b;

Úlohy 6

6a. Máš záujmy dvoch ľudí ako HashSety. Nájdi všetky záujmy dokopy (bez duplikátov).

6b. Napíš funkciu:

fn vsetky_unikatne_znaky(texty: &[String]) -> HashSet<char>

Vráti množinu všetkých znakov zo všetkých textov. Hint: iteruj cez texty a postupne rob union, alebo jednoducho insertuj do jedného HashSetu.


Kapitola 7: Rozdiel (difference)

Rozdiel = prvky v prvej, ktoré nie sú v druhej.

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

// Prvky v A, ktoré NIE SÚ v B
let rozdiel: HashSet<i32> = a.difference(&b).copied().collect();
// {1, 2}

// Opačný smer — prvky v B, ktoré NIE SÚ v A
let rozdiel_opacny: HashSet<i32> = b.difference(&a).copied().collect();
// {5, 6}

Skratka:

let rozdiel: HashSet<i32> = &a - &b;  // {1, 2}

Symetrický rozdiel = prvky v jednej ALEBO druhej, ale nie v oboch:

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

// Skratka:
let sym: HashSet<i32> = &a ^ &b;

Úlohy 7

7a. Máš zoznam všetkých študentov a zoznam prítomných. Nájdi kto chýba:

let vsetci: HashSet<String> = /* Anna, Boris, Cyril, Dana, Emil */;
let pritomni: HashSet<String> = /* Anna, Cyril, Emil */;
// Chýba: Boris, Dana

7b. Napíš funkciu:

fn chybajuce_pismena(slovo: &str) -> HashSet<char>

Vráti písmená abecedy (a-z), ktoré sa v slove nenachádzajú. Hint: ('a'..='z').collect() ti dá HashSet celej abecedy.

7c. Máš dva Vec. Nájdi čísla, ktoré sú len v jednom z nich (nie v oboch). Použi symetrický rozdiel.


Kapitola 8: Podmnožina, nadmnožina, disjunkcia

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

// is_subset — je mala podmnožinou velka?
mala.is_subset(&velka);      // true — všetky prvky mala sú vo velka

// is_superset — je velka nadmnožinou mala?
velka.is_superset(&mala);    // true — velka obsahuje všetko z mala

// is_disjoint — nemajú nič spoločné?
mala.is_disjoint(&ina);      // true — žiadny spoločný prvok
mala.is_disjoint(&velka);    // false — majú spoločné 1 a 2

Úlohy 8

8a. Máš povinné predmety a predmety, ktoré študent absolvoval. Napíš funkciu:

fn splnil_vsetky(povinne: &HashSet<String>, absolvovane: &HashSet<String>) -> bool

Vráti true ak absolvoval všetky povinné. Jeden riadok.

8b. Máš dve skupiny ľudí. Napíš funkciu, ktorá zistí, či sa skupiny vôbec neprekrývajú:

fn su_nezavisle(a: &HashSet<String>, b: &HashSet<String>) -> bool

8c. Študent má predmety. Máš set povinných, set voliteľných. Napíš:

  • splnil_povinne(studentove: &HashSet<String>, povinne: &HashSet<String>) -> bool
  • ktore_povinne_chybaju(studentove: &HashSet<String>, povinne: &HashSet<String>) -> HashSet<String> — ktoré povinné mu ešte chýbajú

Kapitola 9: Prehľad operátorov

Všetky množinové operácie majú aj skrátený zápis:

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

let prienik        = &a & &b;   // {2, 3}
let zjednotenie    = &a | &b;   // {1, 2, 3, 4}
let rozdiel        = &a - &b;   // {1}
let sym_rozdiel    = &a ^ &b;   // {1, 4}

Všetky vracajú nový HashSet<i32> (nie referencie). Pozor na &a — treba referencie.

Úlohy 9

9a. Máš tri sety A, B, C. Napíš jedným výrazom s operátormi:

  • prvky, ktoré sú vo všetkých troch
  • prvky, ktoré sú v aspoň jednom
  • prvky, ktoré sú v A ale nie v B ani v C

Kapitola 10: Praktické vzory zo skúšky

Vzor 1: Obesenec — sledovanie uhádnutých písmen

struct Obesenec {
    slovo: String,
    uhadnute: HashSet<char>,
    skusane: HashSet<char>,
    zivoty: u8,
}

impl Obesenec {
    fn new(slovo: &str) -> Obesenec {
        Obesenec {
            slovo: slovo.to_lowercase(),
            uhadnute: HashSet::new(),
            skusane: HashSet::new(),
            zivoty: 6,
        }
    }

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

        // insert vráti false ak písmeno už bolo skúšané
        if !self.skusane.insert(p) {
            return "Už si skúšal";
        }

        if self.slovo.contains(p) {
            self.uhadnute.insert(p);
            "Správne"
        } else {
            self.zivoty -= 1;
            "Zle"
        }
    }

    // Zobraz slovo: uhádnuté písmená, neuhádnuté ako '_'
    fn zobraz(&self) -> String {
        self.slovo.chars().map(|c| {
            if self.uhadnute.contains(&c) { c } else { '_' }
        }).collect()
    }

    // Vyhral ak všetky písmená slova sú v uhadnute
    fn vyhral(&self) -> bool {
        self.slovo.chars().all(|c| self.uhadnute.contains(&c))
    }
}

Kľúčové veci:

  • skusane.insert(p) vráti false → písmeno už bolo → preskočíme
  • uhadnute.contains(&c) → rýchla kontrola pre každý znak slova
  • slovo.chars().all(|c| uhadnute.contains(&c)) → vyhral ak uhádol všetky

Vzor 2: Unikátne hodnoty z kolekcie štruktúr

struct Kniha { nazov: String, zaner: String }

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

Vzor 3: Rýchle vyhľadávanie v cykle

// Ak kontroluješ contains opakovane, preveď Vec na HashSet
fn najdi_spolocne(kniznica: &[Kniha], pozicane_nazvy: &[String]) -> Vec<&Kniha> {
    let nazvy_set: HashSet<&String> = pozicane_nazvy.iter().collect();
    kniznica.iter()
        .filter(|k| nazvy_set.contains(&k.nazov))
        .collect()
}

Úlohy 10

10a. Implementuj Obesenca sám od nuly. Musíš mať:

  • new(slovo: &str) -> Obesenec
  • tipni(&mut self, pismeno: char) -> bool — true ak správne
  • zobraz(&self) -> String — slovo s '_' za neuhádnuté
  • je_koniec(&self) -> bool — true ak vyhral alebo zivoty == 0

10b. Máš vektor študentov s predmety: Vec<String>. Napíš:

  • vsetky_predmety(studenti: &[Student]) -> HashSet<String>
  • studenti_s_predmetom(studenti: &[Student], predmet: &str) -> Vec<&Student>

10c. Máš povinne_predmety: HashSet<String> a absolvovane: HashSet<String>. Vypíš: koľko povinných splnil, koľko mu chýba, ktoré mu chýbajú.