# Intenzívna príprava na skúšku z Rustu ## Analýza vzoru skúšok Z troch skúšok (Knižnica, Obesenec, Milionár) vyplýva **vždy rovnaký vzor**: 1. **Štruktúry** s derives (Serialize, Deserialize, Default, Clone, PartialEq) 2. **Enumy** s derives + Display 3. **Asociované funkcie** (konštruktory `::new`, `::nacitaj_zo_suboru`) 4. **Metódy** na štruktúrach (CRUD operácie, filtrovanie, štatistiky) 5. **JSON serializácia/deserializácia** cez serde 6. **File I/O** s PathBuf → Option alebo bool 7. **CLI aplikácia** s knižnicou `clap` (subcommands + argumenty) 8. **Trait definícia + implementácia** (IOManager, Display) 9. **Iterátory** (filter, find, map, collect) 10. **Option/Result** return typy všade ### Povolené knižnice - `clap` (derive), `serde` (derive), `serde_json`, `itertools`, `rand` - `chrono` (serde), `futures`, `color-eyre`, `dotenvy` --- ## BLOK 1: Štruktúry, Enumy, Derives (základ všetkého) ### Cvičenie 1.1 — Filmová databáza Implementuj nasledovné: ``` Enum Zaner: - Hodnoty: Akcia, Komedia, Horor, Drama, SciFi, Dokumentarny - Požadované traity: Default, Display, Serialize, Deserialize, PartialEq, Clone - Default vráti hodnotu Drama Štruktúra Film: - nazov: String - reziser: String - rok: u16 - zaner: Zaner - hodnotenie: f32 (0.0 - 10.0) - Požadované traity: Default, Serialize, Deserialize, Clone Štruktúra Filmoteka: - filmy: Vec - Požadované traity: Default, Serialize, Deserialize ``` **Úlohy:** 1. Implementuj `Display` pre `Zaner` (výstup: slovenský názov žánru) 2. Implementuj `Display` pre `Film` (formát: `"Nazov (rok) - Reziser [Zaner] ★hodnotenie"`) 3. Implementuj `Default` pre `Zaner` ### Cvičenie 1.2 — Enum s dátami ``` Enum Stav: - Aktivny - Pozastaveny { dovod: String } - Ukonceny { datum: String, hodnotenie: u8 } - Požadované traity: Serialize, Deserialize, Clone, PartialEq ``` Implementuj `Display` pre `Stav`: - Aktivny → "Aktívny" - Pozastaveny → "Pozastavený: {dovod}" - Ukonceny → "Ukončený dňa {datum} s hodnotením {hodnotenie}" --- ## BLOK 2: Asociované funkcie a Konštruktory ### Cvičenie 2.1 — ::new a ::nacitaj_zo_suboru Pre štruktúru `Filmoteka`: ```rust // Asociovaná funkcia - načíta z JSON súboru // Parameter: &std::path::PathBuf // Návratová hodnota: Option // Ak súbor neexistuje → None // Ak sa nepodarí deserializovať → None fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option // Metóda - uloží do JSON súboru // Parameter: &std::path::PathBuf // Návratová hodnota: bool (true ak úspech) fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool ``` **Kľúčové vzory na zapamätanie:** ```rust // Čítanie súboru let obsah = std::fs::read_to_string(cesta).ok()?; let data: T = serde_json::from_str(&obsah).ok()?; // Zápis do súboru let json = serde_json::to_string_pretty(&self).ok()?; std::fs::write(cesta, json).ok()?; ``` ### Cvičenie 2.2 — Konštruktor s validáciou ```rust // Pre štruktúru Film: // Funkcia ::new berie nazov: &str, reziser: &str, rok: u16, zaner: Zaner, hodnotenie: f32 // Ak hodnotenie nie je v rozsahu 0.0..=10.0, vráti None // Inak vráti Some(Film) fn new(nazov: &str, reziser: &str, rok: u16, zaner: Zaner, hodnotenie: f32) -> Option ``` --- ## BLOK 3: Metódy — CRUD + Filtrovanie + Štatistiky ### Cvičenie 3.1 — CRUD operácie (vzor z Knižnica) Pre `Filmoteka`: ```rust // Pridaj film. Ak film s rovnakým názvom už existuje → Err(()) fn pridaj_film(&mut self, film: Film) -> Result<(), ()> // Odstráň film podľa názvu. Ak neexistuje → Err(()) fn odstran_film(&mut self, nazov: &str) -> Result ``` ### Cvičenie 3.2 — Filtrovanie (vzor z Knižnica) ```rust // Vráti film podľa názvu fn daj_film_podla_nazvu(&self, nazov: &str) -> Option<&Film> // Vráti všetky filmy daného režiséra fn daj_filmy_rezisera(&self, reziser: &str) -> Vec<&Film> // Vráti všetky filmy daného žánru fn daj_filmy_podla_zanru(&self, zaner: &Zaner) -> Vec<&Film> // Vráti filmy s hodnotením vyšším ako zadané fn daj_filmy_nad_hodnotenie(&self, min: f32) -> Vec<&Film> ``` ### Cvičenie 3.3 — Štatistiky (vzor z Knižnica) ```rust // Pre každý žáner vypíše počet filmov // Formát: "Žáner: počet filmov" fn vypis_statistiky_zanrov(&self) // Priemerné hodnotenie filmov daného režiséra fn priemerne_hodnotenie_rezisera(&self, reziser: &str) -> Option ``` **Tip:** Použi `itertools` alebo `HashMap` na zoskupenie: ```rust use std::collections::HashMap; let mut mapa: HashMap<&str, Vec<&Film>> = HashMap::new(); for film in &self.filmy { mapa.entry(&film.reziser).or_default().push(film); } ``` --- ## BLOK 4: Traity (vzor z Obesenec — IOManager) ### Cvičenie 4.1 — Definícia a implementácia traitu ```rust // Trait pre vstup/výstup trait IOManager { fn ziskaj_vstup(&self) -> String; fn zobraz_spravu(&self, sprava: &str); } // Implementuj pre štruktúru ConsoleIO (prázdna štruktúra) struct ConsoleIO; impl IOManager for ConsoleIO { fn ziskaj_vstup(&self) -> String { let mut vstup = String::new(); std::io::stdin().read_line(&mut vstup).unwrap(); vstup.trim().to_string() } fn zobraz_spravu(&self, sprava: &str) { println!("{}", sprava); } } ``` ### Cvičenie 4.2 — Trait s generickým parametrom ```rust // Trait pre validáciu trait Validator { fn je_platny(&self) -> bool; fn chyby(&self) -> Vec; } // Implementuj pre Film: // - hodnotenie musí byť 0.0..=10.0 // - nazov nesmie byť prázdny // - rok musí byť 1888..=2026 ``` --- ## BLOK 5: Hra — Game Loop Pattern (vzor z Obesenec + Milionár) ### Cvičenie 5.1 — Kvízová hra Implementuj jednoduchú kvízovú hru: ``` Štruktúra Odpoved: - text: String - je_spravna: bool - Traity: Serialize, Deserialize, Clone Štruktúra Otazka: - text: String - odpovede: Vec (presne 4) - Traity: Serialize, Deserialize, Clone Štruktúra Kviz: - otazky: Vec - aktualne_skore: u32 - Traity: Serialize, Deserialize, Default ``` **Metódy:** ```rust impl Odpoved { fn new(text: &str, je_spravna: bool) -> Odpoved } impl Otazka { // Vytvorí otázku. Ak odpovede.len() != 4, vráti None // Ak žiadna odpoveď nie je správna, vráti None fn new(text: &str, odpovede: Vec) -> Option // Pridá odpoveď. Ak je už 4, vráti false. fn pridaj_odpoved(&mut self, odpoved: Odpoved) -> bool } impl Kviz { fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool // Vyberie náhodnú otázku fn nahodna_otazka(&self) -> Option<&Otazka> // Vyhodnotí odpoveď (index 0-3), vráti bool fn vyhodnot(&mut self, otazka: &Otazka, index: usize) -> bool // Game loop - používa IOManager trait fn hraj(&mut self, io: &impl IOManager) } ``` ### Cvičenie 5.2 — Implementuj `hraj` metódu Vzor game loopu (toto sa opakuje na KAŽDEJ skúške): ```rust fn hraj(&mut self, io: &impl IOManager) { loop { // 1. Zobraz stav hry io.zobraz_spravu(&format!("Skóre: {}", self.aktualne_skore)); // 2. Vyber otázku let otazka = match self.nahodna_otazka() { Some(o) => o.clone(), None => { io.zobraz_spravu("Žiadne otázky!"); return; } }; // 3. Zobraz otázku + odpovede io.zobraz_spravu(&otazka.text); for (i, odp) in otazka.odpovede.iter().enumerate() { io.zobraz_spravu(&format!(" {}: {}", i + 1, odp.text)); } // 4. Načítaj vstup let vstup = io.ziskaj_vstup(); // 5. Spracuj vstup match vstup.trim().parse::() { Ok(n) if n >= 1 && n <= 4 => { if self.vyhodnot(&otazka, n - 1) { io.zobraz_spravu("Správne!"); } else { io.zobraz_spravu("Nesprávne!"); } } _ => { io.zobraz_spravu("Neplatný vstup!"); continue; } } // 6. Kontrola konca io.zobraz_spravu("Pokračovať? (a/n)"); if io.ziskaj_vstup().trim() == "n" { break; } } io.zobraz_spravu(&format!("Konečné skóre: {}", self.aktualne_skore)); } ``` --- ## BLOK 6: CLI s Clap (toto je na KAŽDEJ skúške za 5-6 bodov!) ### Cvičenie 6.1 — Základný clap vzor ```rust use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(name = "filmoteka")] struct Cli { #[command(subcommand)] prikaz: Prikazy, } #[derive(Subcommand)] enum Prikazy { /// Pridaj nový film Pridaj { /// Cesta k súboru s filmami cesta: std::path::PathBuf, /// Názov filmu nazov: String, /// Režisér reziser: String, /// Rok vydania rok: u16, }, /// Odstráň film Odstran { cesta: std::path::PathBuf, nazov: String, }, /// Zobraz filmy režiséra FilmyRezisera { cesta: std::path::PathBuf, reziser: String, }, /// Štatistiky Statistiky { cesta: std::path::PathBuf, }, } fn main() { let cli = Cli::parse(); match cli.prikaz { Prikazy::Pridaj { cesta, nazov, reziser, rok } => { // 1. Načítaj filmotéku zo súboru (alebo vytvor novú) let mut filmoteka = Filmoteka::nacitaj_zo_suboru(&cesta) .unwrap_or_default(); // 2. Vytvor film a pridaj let film = Film { nazov, reziser, rok, ..Default::default() }; match filmoteka.pridaj_film(film) { Ok(()) => { filmoteka.uloz_do_suboru(&cesta); println!("Film pridaný."); } Err(()) => println!("Film už existuje."), } } Prikazy::Odstran { cesta, nazov } => { // podobne... } // ... ďalšie príkazy } } ``` ### Cvičenie 6.2 — Clap s viacerými argumentmi Vzor príkazu s 3 argumentmi (ako v Milionár a Obesenec): ```rust #[derive(Subcommand)] enum Prikazy { PridajOtazku { /// Cesta k JSON súboru cesta: std::path::PathBuf, /// Text otázky otazka: String, }, Hraj { /// Cesta k JSON súboru cesta: std::path::PathBuf, /// Kategória kategoria: String, }, } ``` --- ## BLOK 7: HashSet + Iterátory (vzor z Obesenec) ### Cvičenie 7.1 — Práca s HashSet ```rust use std::collections::HashSet; struct SlovnaHra { hladane_slovo: String, uhadnute_pismena: HashSet, skusane_pismena: HashSet, pocet_zivotov: u8, } impl SlovnaHra { fn new(slovo: &str) -> SlovnaHra { SlovnaHra { hladane_slovo: slovo.to_lowercase(), uhadnute_pismena: HashSet::new(), skusane_pismena: HashSet::new(), pocet_zivotov: 6, } } // Vráti slovo s neuhádnutými písmenami nahradenými '_' fn daj_slovo_skryto(&self) -> String { self.hladane_slovo.chars().map(|c| { if self.uhadnute_pismena.contains(&c) { c } else { '_' } }).collect() } // Tipni písmeno - vráti enum VysledokPokusu fn tipni_pismeno(&mut self, pismeno: char) -> VysledokPokusu { let p = pismeno.to_lowercase().next().unwrap(); if self.skusane_pismena.contains(&p) { return VysledokPokusu::Neplatne; } self.skusane_pismena.insert(p); if self.hladane_slovo.contains(p) { self.uhadnute_pismena.insert(p); VysledokPokusu::Uhadnute } else { self.pocet_zivotov -= 1; VysledokPokusu::Neuhadnute } } fn je_koniec_hry(&self) -> bool { self.pocet_zivotov == 0 || self.je_vyhra() } fn je_vyhra(&self) -> bool { self.hladane_slovo.chars().all(|c| self.uhadnute_pismena.contains(&c)) } } ``` ### Cvičenie 7.2 — Iterátory s impl Iterator ```rust // Vráti iterátor na uhádnuté písmená fn daj_uhadnute_pismena(&self) -> impl Iterator { self.uhadnute_pismena.iter() } // Vráti iterátor na skúšané písmená fn daj_skusane_pismena(&self) -> impl Iterator { self.skusane_pismena.iter() } ``` --- ## BLOK 8: rand — Náhodné generovanie ### Cvičenie 8.1 ```rust use rand::Rng; use rand::seq::SliceRandom; // Vyber náhodné slovo z vektora fn nahodne_slovo(slova: &[String]) -> Option<&String> { let mut rng = rand::thread_rng(); slova.choose(&mut rng) } // Náhodné číslo v rozsahu fn nahodne_cislo(min: u32, max: u32) -> u32 { let mut rng = rand::thread_rng(); rng.gen_range(min..=max) } // Zamiešaj vektor fn zamiesaj(vektor: &mut Vec) { let mut rng = rand::thread_rng(); vektor.shuffle(&mut rng); } ``` --- ## BLOK 9: Nové knižnice — chrono, color-eyre, futures ### Cvičenie 9.1 — chrono (dátumy) ```rust use chrono::{NaiveDate, Local, Datelike}; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Clone)] struct Udalost { nazov: String, datum: NaiveDate, // chrono s serde feature } impl Udalost { fn new(nazov: &str, rok: i32, mesiac: u32, den: u32) -> Option { let datum = NaiveDate::from_ymd_opt(rok, mesiac, den)?; Some(Udalost { nazov: nazov.to_string(), datum }) } fn je_dnes(&self) -> bool { self.datum == Local::now().date_naive() } fn dni_do_udalosti(&self) -> i64 { let dnes = Local::now().date_naive(); (self.datum - dnes).num_days() } } ``` ### Cvičenie 9.2 — color-eyre (error handling) ```rust use color_eyre::eyre::{Result, eyre, WrapErr}; fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Result { let obsah = std::fs::read_to_string(cesta) .wrap_err_with(|| format!("Nepodarilo sa otvoriť súbor: {:?}", cesta))?; let filmoteka: Filmoteka = serde_json::from_str(&obsah) .wrap_err("Nepodarilo sa deserializovať JSON")?; Ok(filmoteka) } fn main() -> Result<()> { color_eyre::install()?; // ... zvyšok kódu s ? operátorom Ok(()) } ``` --- ## BLOK 10: Kompletné cvičné zadanie — Správca úloh (TODO app) **Toto je simulácia skúšky. Skús to celé implementovať za 90 minút.** ### Zadanie: Správca úloh Cieľom je implementovať aplikáciu na správu úloh s nasledovnými funkcionalitami: - pridávanie úloh - odstraňovanie úloh - označovanie úloh ako dokončené - filtrovanie úloh podľa rôznych kritérií - štatistiky - načítavanie a ukladanie do súboru vo formáte JSON #### Enum Priorita - Nazov: Priorita - Hodnoty: Nizka, Stredna, Vysoka, Kriticka - Požadované traity: Default, Display, Serialize, Deserialize, PartialEq, Clone - Default vráti hodnotu Stredna #### Enum StavUlohy - Hodnoty: Nova, Rozpracovana, Dokoncena, Zrusena - Požadované traity: Default, Display, Serialize, Deserialize, PartialEq, Clone - Default vráti hodnotu Nova #### Štruktúra Uloha - nazov: String - popis: String - priorita: Priorita - stav: StavUlohy - kategoria: String - Požadované traity: Default, Serialize, Deserialize, Clone #### Štruktúra SpravcaUloh - ulohy: Vec - Požadované traity: Default, Serialize, Deserialize #### Funkcia SpravcaUloh::nacitaj_zo_suboru - Parameter: &PathBuf → Option #### Metóda spravca_uloh.uloz_do_suboru - Parameter: &PathBuf → bool #### Metóda spravca_uloh.pridaj_ulohu - Parameter: Uloha → Result<(), ()> - Err ak úloha s rovnakým názvom existuje #### Metóda spravca_uloh.odstran_ulohu - Parameter: &str (názov) → Result #### Metóda spravca_uloh.oznac_dokoncenu - Parameter: &str (názov) → Result<(), ()> - Zmení stav úlohy na Dokoncena #### Metóda spravca_uloh.daj_ulohy_podla_priority - Parameter: &Priorita → Vec<&Uloha> #### Metóda spravca_uloh.daj_ulohy_podla_stavu - Parameter: &StavUlohy → Vec<&Uloha> #### Metóda spravca_uloh.daj_ulohy_podla_kategorie - Parameter: &str → Vec<&Uloha> #### Metóda spravca_uloh.vypis_statistiky_kategorii - Pre každú kategóriu vypíše počet úloh - Formát: "Kategória: počet úloh" #### Metóda spravca_uloh.vypis_ulohy_podla_priority - Vypíše všetky úlohy zoskupené podľa priority #### Ovládanie aplikácie (main.rs s clap) Príkazy: - `pridaj` — 2 argumenty: cesta k súboru, názov úlohy. Interaktívne sa opýta na popis, prioritu a kategóriu. - `odstran` — 2 argumenty: cesta k súboru, názov úlohy - `dokonci` — 2 argumenty: cesta k súboru, názov úlohy - `zoznam` — 2 argumenty: cesta k súboru, filter (vsetky/dokoncene/nedokoncene) - `statistiky` — 1 argument: cesta k súboru --- ## BLOK 11: Ďalšie simulácie skúšok ### Simulácia 2 — Správca kontaktov ``` Enum TypKontaktu: Osobny, Pracovny, Rodina Štruktúra Kontakt: meno, priezvisko, email, telefon, typ_kontaktu Štruktúra Adresar: kontakty: Vec Metódy: - nacitaj_zo_suboru, uloz_do_suboru - pridaj_kontakt (duplicita podľa emailu → Err) - odstran_kontakt (podľa emailu) - najdi_podla_mena(&str) → Vec<&Kontakt> - najdi_podla_typu(&TypKontaktu) → Vec<&Kontakt> - vypis_kontakty_podla_typu() CLI: pridaj, odstran, hladaj-meno, hladaj-typ, statistiky ``` ### Simulácia 3 — Hudobná knižnica (s chrono) ``` Enum Zaner: Rock, Pop, Jazz, Klasicka, HipHop, Elektronicka Štruktúra Piesnicka: nazov, interpret, zaner, trvanie_sekundy: u32, datum_pridania: NaiveDate Štruktúra Playlist: nazov, piesnicky: Vec Štruktúra HudobnaKniznica: playlisty: Vec Metódy pre Playlist: - pridaj_piesnicku, odstran_piesnicku - celkove_trvanie() → u32 - piesnicky_interpreta(&str) → Vec<&Piesnicka> Metódy pre HudobnaKniznica: - nacitaj_zo_suboru, uloz_do_suboru - vytvor_playlist, odstran_playlist - najdi_piesnicku_v_kniznici(&str) → Vec<(&Playlist, &Piesnicka)> - statistiky_zanrov() CLI: vytvor-playlist, pridaj-piesnicku (3 arg: cesta, playlist, piesnicka), zoznam, statistiky ``` ### Simulácia 4 — Hra s kartami (trait + rand) ``` Trait Hratelna { fn tahaj_kartu(&mut self) -> Option; fn je_koniec(&self) -> bool; fn daj_skore(&self) -> u32; } Enum Farba: Srdce, Karo, Piky, Krize (Display) Enum Hodnota: Cislo(u8), Dolnik, Hornik, Kral, Eso (Display) Štruktúra Karta: farba, hodnota Štruktúra Balicek: karty: Vec Štruktúra HraBlackjack: balicek, hrac_karty, dealer_karty, stav_hry impl Balicek: - fn novy() → Balicek // vytvorí 32 kariet - fn zamiesaj(&mut self) - fn tahaj(&mut self) -> Option impl HraBlackjack: Hratelna - fn hraj(&mut self, io: &impl IOManager) CLI: hraj (cesta k uloženej hre), nove-hry ``` --- ## Kľúčové vzory na zapamätanie (cheat sheet) ### 1. Serde JSON čítanie/zápis ```rust // Čítanie let obsah = std::fs::read_to_string(&cesta).ok()?; let data: MojTyp = serde_json::from_str(&obsah).ok()?; Some(data) // Zápis let json = serde_json::to_string_pretty(&self).unwrap_or_default(); std::fs::write(&cesta, json).is_ok() ``` ### 2. Vec filtrovanie ```rust // Referencia self.polozky.iter().filter(|p| p.nazov == hladany).collect() // Hľadanie jedného self.polozky.iter().find(|p| p.nazov == hladany) // Odstránenie if let Some(pos) = self.polozky.iter().position(|p| p.nazov == hladany) { Ok(self.polozky.remove(pos)) } else { Err(()) } ``` ### 3. Display implementácia ```rust impl std::fmt::Display for MojEnum { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { MojEnum::Varianta1 => write!(f, "Text 1"), MojEnum::Varianta2 => write!(f, "Text 2"), } } } ``` ### 4. Clap šablóna ```rust use clap::{Parser, Subcommand}; #[derive(Parser)] struct Cli { #[command(subcommand)] prikaz: Prikazy, } #[derive(Subcommand)] enum Prikazy { NazovPrikazu { cesta: std::path::PathBuf, argument: String, }, } fn main() { let cli = Cli::parse(); match cli.prikaz { Prikazy::NazovPrikazu { cesta, argument } => { /* ... */ } } } ``` ### 5. HashMap štatistiky ```rust use std::collections::HashMap; let mut mapa: HashMap = HashMap::new(); for polozka in &self.polozky { *mapa.entry(polozka.kategoria.clone()).or_insert(0) += 1; } for (kluc, pocet) in &mapa { println!("{}: {}", kluc, pocet); } ``` ### 6. rand ```rust use rand::seq::SliceRandom; let mut rng = rand::thread_rng(); let nahodny = vektor.choose(&mut rng); vektor.shuffle(&mut rng); ``` ### 7. Čítanie vstupu z konzoly ```rust let mut vstup = String::new(); std::io::stdin().read_line(&mut vstup).unwrap(); let vstup = vstup.trim(); // Parsovanie na číslo: let cislo: usize = vstup.parse().unwrap_or(0); ``` --- ## Postup na skúške 1. **Prečítaj celé zadanie** (2 min) 2. **Vytvor `Cargo.toml`** s potrebnými závislosťami (1 min) 3. **Implementuj enumy a štruktúry** s derives v `lib.rs` (10 min) 4. **Implementuj `Display`** pre enumy (5 min) 5. **Implementuj `nacitaj_zo_suboru` + `uloz_do_suboru`** (5 min) — toto je vždy rovnaké! 6. **Implementuj CRUD metódy** (15 min) 7. **Implementuj filtrovacie metódy** (10 min) 8. **Implementuj štatistiky/špeciálne metódy** (10 min) 9. **Implementuj game loop** ak je hra (15 min) 10. **Implementuj CLI v `main.rs`** (15 min) 11. **Testuj s `cargo test`** + manuálne (zvyšok) **CELKOM: ~90 minút**