# HashSet — krok za krokom Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej. ```rust 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). ```rust // Prázdny set let mut mnozina: HashSet = HashSet::new(); // Alebo nechaj Rust odvodiť: let mut mnozina = HashSet::new(); mnozina.insert(1); // teraz Rust vie: HashSet // Z vektora — duplikáty zmiznú! let cisla = vec![1, 2, 3, 2, 1, 4, 3, 5]; let unikatne: HashSet = cisla.into_iter().collect(); // {1, 2, 3, 4, 5} — len 5 prvkov, nie 8 // Z reťazca — množina znakov let znaky: HashSet = "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: ```rust let v = vec![10, 20, 10, 30, 20, 10]; let s: HashSet = 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 ```rust 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:** ```rust 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` — 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: ```rust 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 ```rust 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. ```rust // 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: ```rust fn je_povolena(povolene: &HashSet, 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()`: ```rust // retain nechá len prvky, kde closure vráti true mnozina.retain(|meno| meno.len() >= 4); ``` --- ## Kapitola 4: Iterácia ```rust let mnozina: HashSet = 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` — súčet všetkých prvkov. **4c.** Napíš funkciu `najdlhsi(mnozina: &HashSet) -> 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. ```rust let a: HashSet = vec![1, 2, 3, 4].into_iter().collect(); let b: HashSet = 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 = a.intersection(&b).copied().collect(); // {3, 4} // Alebo do Vec let prienik: Vec<&i32> = a.intersection(&b).collect(); // [&3, &4] ``` **Skratka operátorom:** ```rust let prienik: HashSet = &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á. ```rust let trieda_a = vec!["Anna", "Boris", "Cyril", "Dana"]; let trieda_b = vec!["Boris", "Dana", "Emil", "Fero"]; // Spoločné: Boris, Dana ``` **5b.** Napíš funkciu: ```rust fn spolocne_pismena(slovo1: &str, slovo2: &str) -> HashSet ``` 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ú. ```rust let a: HashSet = vec![1, 2, 3].into_iter().collect(); let b: HashSet = vec![3, 4, 5].into_iter().collect(); let zjednotenie: HashSet<&i32> = a.union(&b).collect(); // {&1, &2, &3, &4, &5} let zjednotenie: HashSet = a.union(&b).copied().collect(); // {1, 2, 3, 4, 5} ``` **Skratka:** ```rust let zjednotenie: HashSet = &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: ```rust fn vsetky_unikatne_znaky(texty: &[String]) -> HashSet ``` 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**. ```rust let a: HashSet = vec![1, 2, 3, 4].into_iter().collect(); let b: HashSet = vec![3, 4, 5, 6].into_iter().collect(); // Prvky v A, ktoré NIE SÚ v B let rozdiel: HashSet = a.difference(&b).copied().collect(); // {1, 2} // Opačný smer — prvky v B, ktoré NIE SÚ v A let rozdiel_opacny: HashSet = b.difference(&a).copied().collect(); // {5, 6} ``` **Skratka:** ```rust let rozdiel: HashSet = &a - &b; // {1, 2} ``` **Symetrický rozdiel** = prvky v jednej ALEBO druhej, ale nie v oboch: ```rust let sym: HashSet = a.symmetric_difference(&b).copied().collect(); // {1, 2, 5, 6} // Skratka: let sym: HashSet = &a ^ &b; ``` ### Úlohy 7 **7a.** Máš zoznam všetkých študentov a zoznam prítomných. Nájdi kto chýba: ```rust let vsetci: HashSet = /* Anna, Boris, Cyril, Dana, Emil */; let pritomni: HashSet = /* Anna, Cyril, Emil */; // Chýba: Boris, Dana ``` **7b.** Napíš funkciu: ```rust fn chybajuce_pismena(slovo: &str) -> HashSet ``` 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 ```rust let mala: HashSet = vec![1, 2].into_iter().collect(); let velka: HashSet = vec![1, 2, 3, 4].into_iter().collect(); let ina: HashSet = 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: ```rust fn splnil_vsetky(povinne: &HashSet, absolvovane: &HashSet) -> 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ú: ```rust fn su_nezavisle(a: &HashSet, b: &HashSet) -> bool ``` **8c.** Študent má predmety. Máš set povinných, set voliteľných. Napíš: - `splnil_povinne(studentove: &HashSet, povinne: &HashSet) -> bool` - `ktore_povinne_chybaju(studentove: &HashSet, povinne: &HashSet) -> HashSet` — 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: ```rust let a: HashSet = vec![1, 2, 3].into_iter().collect(); let b: HashSet = 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` (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 ```rust struct Obesenec { slovo: String, uhadnute: HashSet, skusane: HashSet, 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 ```rust struct Kniha { nazov: String, zaner: String } fn unikatne_zanre(knihy: &[Kniha]) -> HashSet { knihy.iter().map(|k| k.zaner.clone()).collect() } ``` ### Vzor 3: Rýchle vyhľadávanie v cykle ```rust // 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`. Napíš: - `vsetky_predmety(studenti: &[Student]) -> HashSet` - `studenti_s_predmetom(studenti: &[Student], predmet: &str) -> Vec<&Student>` **10c.** Máš `povinne_predmety: HashSet` a `absolvovane: HashSet`. Vypíš: koľko povinných splnil, koľko mu chýba, ktoré mu chýbajú.