diff --git a/priprava/rust_priprava13.md b/priprava/rust_priprava13.md new file mode 100644 index 0000000..b888a7c --- /dev/null +++ b/priprava/rust_priprava13.md @@ -0,0 +1,820 @@ +# I/O v Ruste — krok za krokom + +Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej. + +--- + +## Kapitola 1: println! a print! — výstup + +```rust +// println! — vypíše + nový riadok +println!("Ahoj svet"); +// Ahoj svet\n + +// print! — vypíše BEZ nového riadku +print!("Zadaj meno: "); +// Zadaj meno: _ (kurzor zostane na tom istom riadku) +``` + +### Formátovanie + +```rust +let meno = "Anna"; +let vek = 25; +let priemer = 1.847; + +// Základné +println!("{} má {} rokov", meno, vek); +// Anna má 25 rokov + +// Zaokrúhlenie na 2 desatinné +println!("Priemer: {:.2}", priemer); +// Priemer: 1.85 + +// Zarovnanie +println!("{:<10} | {:>5}", "Meno", "Vek"); +println!("{:<10} | {:>5}", "Anna", 25); +println!("{:<10} | {:>5}", "Boris", 30); +// Meno | Vek +// Anna | 25 +// Boris | 30 + +// Padding znakom +println!("{:-^20}", "MENU"); +// --------MENU-------- + +// Debug výpis +let vektor = vec![1, 2, 3]; +println!("{:?}", vektor); // [1, 2, 3] +println!("{:#?}", vektor); // pekný formát na viac riadkov +``` + +### eprintln! — chybový výstup + +```rust +// eprintln! ide na stderr (nie stdout) +eprintln!("Chyba: súbor neexistuje"); + +// V praxi: normálne správy cez println!, chyby cez eprintln! +``` + +### Úlohy 1 + +**1a.** Vypíš tabuľku 3 produktov s názvom (zarovnaný vľavo na 15), cenou (zarovnaná vpravo na 8, 2 desatinné) a počtom (zarovnaný vpravo na 5): +``` +Chlieb | 1.20 | 50 +Mlieko | 0.89 | 120 +Maslo | 2.49 | 30 +``` + +**1b.** Máš Vec<(String, f64)> — mená a priemery. Vypíš ich zoradené podľa priemeru, formátované na 1 desatinné miesto. + +--- + +## Kapitola 2: stdin — čítanie vstupu + +```rust +use std::io; + +fn main() { + // Základné čítanie riadku + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + // vstup teraz obsahuje text + '\n' na konci! + + let vstup = vstup.trim(); // odstráň \n a medzery + println!("Napísal si: {}", vstup); +} +``` + +### Krok po kroku + +```rust +// 1. Vytvor prázdny String (buffer) +let mut vstup = String::new(); + +// 2. Prečítaj riadok do bufferu +// read_line PRIDÁVA do stringu (neprepisuje!) +// Vracia Result (počet prečítaných bajtov) +io::stdin().read_line(&mut vstup).unwrap(); + +// 3. VŽDY trimni — read_line pridáva '\n' +let vstup = vstup.trim(); +``` + +### Čítanie čísla + +```rust +fn nacitaj_cislo() -> Option { + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).ok()?; + vstup.trim().parse().ok() +} + +// Použitie: +match nacitaj_cislo() { + Some(n) => println!("Číslo: {}", n), + None => println!("To nie je číslo"), +} +``` + +### Čítanie jedného znaku + +```rust +fn nacitaj_znak() -> Option { + let mut vstup = String::new(); + std::io::stdin().read_line(&mut vstup).ok()?; + vstup.trim().chars().next() +} + +// Použitie: +match nacitaj_znak() { + Some(c) => println!("Znak: {}", c), + None => println!("Prázdny vstup"), +} +``` + +### Pozor: read_line pridáva, neprepisuje! + +```rust +let mut vstup = String::new(); +io::stdin().read_line(&mut vstup).unwrap(); // "ahoj\n" +io::stdin().read_line(&mut vstup).unwrap(); // "ahoj\nsvet\n" — PRIDALO! + +// Ak chceš čítať viackrát, buď trimuj alebo vytvor nový String +let mut vstup = String::new(); +io::stdin().read_line(&mut vstup).unwrap(); +let prvy = vstup.trim().to_string(); + +vstup.clear(); // VYMAŽ pred ďalším čítaním! +io::stdin().read_line(&mut vstup).unwrap(); +let druhy = vstup.trim().to_string(); +``` + +### Úlohy 2 + +**2a.** Napíš program, ktorý prečíta meno a vek od používateľa a vypíše `"Ahoj Anna, máš 25 rokov"`. Vek parsuj na u32. + +**2b.** Napíš funkciu `nacitaj_riadok() -> Option` — prečíta riadok, trimne, vráti None ak je prázdny. + +**2c.** Napíš funkciu `nacitaj_float() -> Option` — prečíta riadok, trimne, parsne na f64. + +**2d.** Napíš program, ktorý opakovane číta čísla od používateľa kým nezadá "koniec". Na konci vypíše súčet. + +--- + +## Kapitola 3: Game loop — opakované čítanie + +Na skúške je typický vzor: čítaj vstup v cykle, reaguj, skonči keď podmienka. + +### Základný game loop + +```rust +use std::io; + +fn main() { + loop { + print!("Zadaj príkaz: "); + + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + let vstup = vstup.trim(); + + match vstup { + "koniec" => { + println!("Zbohom!"); + break; + } + "pomoc" => { + println!("Príkazy: koniec, pomoc"); + } + _ => { + println!("Neznámy príkaz: {}", vstup); + } + } + } +} +``` + +### Game loop s parsovaním + +```rust +loop { + print!("> "); + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + let casti: Vec<&str> = vstup.trim().split_whitespace().collect(); + + if casti.is_empty() { continue; } + + match casti[0] { + "pridaj" => { + if casti.len() < 2 { + println!("Použitie: pridaj "); + continue; + } + let meno = casti[1]; + println!("Pridávam: {}", meno); + } + "vypis" => { + println!("Výpis..."); + } + "koniec" => break, + _ => println!("Neznámy príkaz"), + } +} +``` + +### Úlohy 3 + +**3a.** Napíš jednoduchý kalkulátor. Používateľ zadáva `"5 + 3"`, `"10 - 4"`, `"koniec"`. Program počíta a vypisuje výsledok. + +**3b.** Napíš program, kde používateľ opakovane háda číslo 1-100. Program povie "viac", "menej" alebo "správne". Použi `rand::Rng`. + +--- + +## Kapitola 4: Súborové I/O — čítanie + +```rust +use std::fs; + +// Prečítaj celý súbor naraz do String +let obsah: Result = fs::read_to_string("data.txt"); + +match obsah { + Ok(text) => println!("Obsah: {}", text), + Err(e) => println!("Chyba: {}", e), +} + +// Skrátene s .ok()? v funkcii +fn nacitaj(cesta: &str) -> Option { + fs::read_to_string(cesta).ok() +} +``` + +### Čítanie po riadkoch + +```rust +use std::fs; + +fn spracuj_subor(cesta: &str) { + let obsah = fs::read_to_string(cesta).unwrap(); + for (i, riadok) in obsah.lines().enumerate() { + println!("{}: {}", i + 1, riadok); + } +} +``` + +### BufReader — efektívne čítanie po riadkoch + +Pre veľké súbory je lepší BufReader — nečíta celý súbor do pamäte: + +```rust +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn citaj_po_riadkoch(cesta: &str) -> Result<(), std::io::Error> { + let subor = File::open(cesta)?; + let citac = BufReader::new(subor); + + for riadok in citac.lines() { + let riadok = riadok?; // každý riadok je Result + println!("{}", riadok); + } + Ok(()) +} +``` + +### Čítanie bajtov + +```rust +// Celý súbor ako bajty +let bajty: Vec = fs::read("obrazok.png").unwrap(); +println!("Veľkosť: {} bajtov", bajty.len()); +``` + +### Úlohy 4 + +**4a.** Napíš funkciu `pocet_riadkov(cesta: &str) -> Option` — prečíta súbor a vráti počet riadkov. + +**4b.** Napíš funkciu `najdi_v_subore(cesta: &str, hladany: &str) -> Vec` — vráti riadky, ktoré obsahujú hľadaný text. + +**4c.** Napíš funkciu `nacitaj_cisla(cesta: &str) -> Option>` — súbor obsahuje jedno číslo na riadok. Prečítaj, parsuj, vráť Vec. + +--- + +## Kapitola 5: Súborové I/O — zápis + +```rust +use std::fs; + +// Zapíš celý String do súboru (prepíše ak existuje!) +fs::write("data.txt", "Ahoj svet\nDruhý riadok\n").unwrap(); + +// Bezpečná verzia v funkcii +fn uloz(cesta: &str, obsah: &str) -> bool { + fs::write(cesta, obsah).is_ok() +} +``` + +### Pridávanie na koniec (append) + +```rust +use std::fs::OpenOptions; +use std::io::Write; + +fn pripis(cesta: &str, text: &str) -> bool { + let vysledok = OpenOptions::new() + .create(true) // vytvor ak neexistuje + .append(true) // pridávaj na koniec + .open(cesta); + + match vysledok { + Ok(mut subor) => { + writeln!(subor, "{}", text).is_ok() + } + Err(_) => false, + } +} +``` + +### BufWriter — efektívny zápis + +```rust +use std::fs::File; +use std::io::{BufWriter, Write}; + +fn zapis_vela_riadkov(cesta: &str, riadky: &[String]) -> bool { + let Ok(subor) = File::create(cesta) else { + return false; + }; + let mut writer = BufWriter::new(subor); + for riadok in riadky { + if writeln!(writer, "{}", riadok).is_err() { + return false; + } + } + true +} +``` + +### Úlohy 5 + +**5a.** Napíš funkciu `uloz_riadky(cesta: &str, riadky: &[String]) -> bool` — ulož vektor riadkov do súboru, každý na nový riadok. + +**5b.** Napíš funkciu `pridaj_riadok(cesta: &str, riadok: &str) -> bool` — pridaj riadok na koniec súboru. + +**5c.** Napíš program, ktorý číta mená od používateľa a ukladá ich do `"mena.txt"`. Keď zadá "koniec", skončí. + +--- + +## Kapitola 6: PathBuf a Path + +`PathBuf` je ako `String` pre cesty k súborom. `Path` je ako `&str`. + +```rust +use std::path::{PathBuf, Path}; + +// Vytváranie PathBuf +let cesta = PathBuf::from("data/subory/kniznica.json"); +let cesta: PathBuf = "data.json".into(); + +// Z &str +let cesta = Path::new("data.json"); + +// Konverzia +let pathbuf = PathBuf::from("data.json"); +let path: &Path = &pathbuf; // PathBuf → &Path +let path: &Path = pathbuf.as_path(); // to isté + +// Užitočné metódy +let cesta = PathBuf::from("/home/user/data.json"); +cesta.exists(); // existuje súbor? +cesta.is_file(); // je to súbor? +cesta.is_dir(); // je to priečinok? +cesta.extension(); // Some("json") +cesta.file_name(); // Some("data.json") +cesta.parent(); // Some("/home/user") + +// Skladanie ciest +let mut cesta = PathBuf::from("/home/user"); +cesta.push("dokumenty"); +cesta.push("subor.txt"); +// /home/user/dokumenty/subor.txt +``` + +### Na skúške — PathBuf v clape + +```rust +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser)] +struct Args { + #[arg(short, long, default_value = "data.json")] + subor: PathBuf, +} + +fn main() { + let args = Args::parse(); + // args.subor je PathBuf — predávaš priamo do funkcií + let kniznica = Kniznica::nacitaj_zo_suboru(&args.subor) + .unwrap_or_default(); +} +``` + +### Funkcie berúce cestu + +```rust +// Berie &PathBuf — funguje s PathBuf +fn nacitaj(cesta: &PathBuf) -> Option { + std::fs::read_to_string(cesta).ok() +} + +// Lepšie: berie &Path — funguje s PathBuf aj &str +fn nacitaj(cesta: &Path) -> Option { + std::fs::read_to_string(cesta).ok() +} + +// fs::read_to_string berie impl AsRef — prijme takmer čokoľvek +// Preto funguje: +fs::read_to_string("data.json"); // &str +fs::read_to_string(String::from("data.json")); // String +fs::read_to_string(PathBuf::from("data.json")); // PathBuf +fs::read_to_string(&pathbuf); // &PathBuf +``` + +### Úlohy 6 + +**6a.** Vytvor PathBuf z `"/home/user/docs"`. Pridaj `"projekt"` a `"data.json"`. Vypíš celú cestu. + +**6b.** Máš PathBuf. Vypíš: existuje? Je súbor? Prípona? Názov súboru? Rodičovský priečinok? + +**6c.** Prečo na skúške metóda `nacitaj_zo_suboru` berie `&PathBuf` a nie `&str`? + +--- + +## Kapitola 7: serde JSON I/O — kompletný vzor + +Toto je vzor na každej skúške. Kombinácia fs + serde_json. + +```rust +use std::fs; +use std::path::PathBuf; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Default)] +struct Kniznica { + knihy: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +struct Kniha { + nazov: String, + autor: String, +} + +impl Kniznica { + // ČÍTANIE: súbor → String → štruktúra + fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option { + // 1. fs::read_to_string — súbor → String + let raw = fs::read_to_string(cesta).ok()?; + // 2. serde_json::from_str — String → štruktúra + serde_json::from_str(&raw).ok() + } + + // ZÁPIS: štruktúra → String → súbor + fn uloz_do_suboru(&self, cesta: &PathBuf) -> bool { + // 1. serde_json::to_string_pretty — štruktúra → String + let Ok(json) = serde_json::to_string_pretty(&self) else { + return false; + }; + // 2. fs::write — String → súbor + fs::write(cesta, json).is_ok() + } +} +``` + +Celý tok dát: + +``` +ČÍTANIE: + súbor na disku + → fs::read_to_string → String s JSON textom + → serde_json::from_str → Rust štruktúra + +ZÁPIS: + Rust štruktúra + → serde_json::to_string_pretty → String s JSON textom + → fs::write → súbor na disku +``` + +### Použitie v main + +```rust +fn main() { + let cesta = PathBuf::from("kniznica.json"); + + // Načítaj alebo vytvor prázdnu + let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) + .unwrap_or_default(); + + // Urob niečo... + kniznica.knihy.push(Kniha { + nazov: "Duna".into(), + autor: "Herbert".into(), + }); + + // Ulož + kniznica.uloz_do_suboru(&cesta); +} +``` + +### Čo sa deje keď súbor neexistuje + +```rust +// fs::read_to_string("neexistuje.json") +// → Err(io::Error) +// → .ok() → None +// → ? → vráti None z funkcie + +// Preto: +Kniznica::nacitaj_zo_suboru(&PathBuf::from("neexistuje.json")) +// → None + +// A v main: +let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) + .unwrap_or_default(); +// None → unwrap_or_default() → Kniznica::default() → Kniznica { knihy: vec![] } +``` + +### Úlohy 7 + +**7a.** Napíš `nacitaj_zo_suboru` a `uloz_do_suboru` pre: +```rust +#[derive(Serialize, Deserialize, Default)] +struct TodoList { + ulohy: Vec, +} +#[derive(Serialize, Deserialize, Clone)] +struct Todo { + text: String, + hotova: bool, +} +``` + +**7b.** Napíš program, ktorý: +1. Načíta TodoList z "todo.json" +2. Pridá novú úlohu +3. Uloží späť +4. Znova načíta a vypíše počet úloh (overenie) + +**7c.** Čo sa stane ak `serde_json::to_string_pretty` zlyhá? Kedy sa to stane? (Hint: takmer nikdy pre bežné štruktúry.) + +--- + +## Kapitola 8: IOManager trait — vzor zo skúšky + +Na skúške sa I/O abstrahuje cez trait. Toto umožňuje testovanie bez konzoly. + +### Prečo nie priamo stdin? + +```rust +// PROBLÉM: toto nejde testovať automaticky +fn hraj(hra: &mut Hra) { + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); // čaká na konzolu + // ... +} +``` + +### Riešenie: IOManager trait + +```rust +// 1. Definuj trait — AKO získať vstup +pub trait IOManager { + fn ziskaj_pismeno(&mut self) -> char; +} + +// 2. Reálna implementácia — čítaj z konzoly +pub struct ConsoleIOManager; + +impl IOManager for ConsoleIOManager { + fn ziskaj_pismeno(&mut self) -> char { + let mut vstup = String::new(); + std::io::stdin().read_line(&mut vstup).unwrap(); + vstup.trim().chars().next().unwrap_or(' ') + } +} + +// 3. Metóda berie impl IOManager — funguje s čímkoľvek +impl Hra { + pub fn hraj(&mut self, mut io: impl IOManager) { + while !self.je_koniec_hry() { + println!("Slovo: {}", self.daj_slovo_skryto()); + let pismeno = io.ziskaj_pismeno(); + let vysledok = self.tipni_pismeno(pismeno); + println!("{}", vysledok); + } + } +} + +// 4. Volanie v main +fn main() { + let mut hra = Hra::new("rust"); + hra.hraj(ConsoleIOManager); // predáme implementáciu +} +``` + +### ConsoleIOManager — prázdna štruktúra + +```rust +// Prázdna štruktúra — žiadne členy +pub struct ConsoleIOManager; + +// Toto nie je typ s poľami. Je to "unit struct". +// Vytváraš ju bez {} : +let io = ConsoleIOManager; // OK +// let io = ConsoleIOManager {}; // tiež OK ale zbytočné +``` + +### Rozšírený IOManager + +Niekedy trait má viac metód: + +```rust +pub trait IOManager { + fn ziskaj_pismeno(&mut self) -> char; + fn zobraz_spravu(&self, sprava: &str); + fn zobraz_stav(&self, zivoty: u8, slovo: &str, skusane: &[char]); +} + +pub struct ConsoleIOManager; + +impl IOManager for ConsoleIOManager { + fn ziskaj_pismeno(&mut self) -> char { + print!("Zadaj písmeno: "); + let mut vstup = String::new(); + std::io::stdin().read_line(&mut vstup).unwrap(); + vstup.trim().chars().next().unwrap_or(' ') + } + + fn zobraz_spravu(&self, sprava: &str) { + println!("{}", sprava); + } + + fn zobraz_stav(&self, zivoty: u8, slovo: &str, skusane: &[char]) { + println!("Životov: {}", zivoty); + println!("Slovo: {}", slovo); + println!("Skúšané: {:?}", skusane); + } +} +``` + +### Úlohy 8 + +**8a.** Napíš trait `Vstup` s metódou `nacitaj_cislo(&mut self) -> Option`. Implementuj: +- `KonzolaVstup` — číta z stdin +- `TestovaciVstup { cisla: Vec, index: usize }` — vracia predpripravené čísla + +**8b.** Napíš funkciu `hadaj_cislo(tajne: i32, mut vstup: impl Vstup)` — hra na hádanie čísla. Používa trait namiesto priameho stdin. + +**8c.** Máš IOManager z príkladu vyššie. Napíš `TestIOManager`: +```rust +struct TestIOManager { + odpovede: Vec, + index: usize, +} +``` +Implementuj IOManager — `ziskaj_pismeno` vráti ďalšie písmeno z `odpovede`. + +--- + +## Kapitola 9: flush — print! bez nového riadku + +`print!` nevyprázdni buffer automaticky. Ak chceš aby sa text zobrazil okamžite pred čítaním vstupu: + +```rust +use std::io::{self, Write}; + +fn main() { + print!("Zadaj meno: "); + io::stdout().flush().unwrap(); // DÔLEŽITÉ! Vynúti výpis + + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + println!("Ahoj, {}!", vstup.trim()); +} +``` + +Bez `flush()` sa "Zadaj meno: " môže zobraziť až po tom, čo zadáš vstup. S `flush()` sa zobrazí hneď. + +```rust +// Kompletný vzor pre prompt +fn prompt(text: &str) -> String { + print!("{}", text); + io::stdout().flush().unwrap(); + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + vstup.trim().to_string() +} + +// Použitie: +let meno = prompt("Meno: "); +let vek = prompt("Vek: "); +``` + +### Úlohy 9 + +**9a.** Napíš funkciu `prompt(text: &str) -> String` s flush. Použi ju na prečítanie mena a veku. + +**9b.** Napíš funkciu `prompt_cislo(text: &str) -> Option` — zobrazí text, prečíta, parsne. + +**9c.** Napíš interaktívny program, ktorý používa prompt funkciu na čítanie príkazov v cykle. + +--- + +## Kapitola 10: Prehľad a vzory + +### Rýchly prehľad: čo kedy použiť + +| Chcem... | Použi | +|----------|-------| +| Čítať celý súbor | `fs::read_to_string(cesta)` | +| Zapísať celý súbor | `fs::write(cesta, obsah)` | +| Čítať po riadkoch (malý súbor) | `fs::read_to_string` + `.lines()` | +| Čítať po riadkoch (veľký súbor) | `BufReader::new(File::open)` + `.lines()` | +| Pripísať na koniec | `OpenOptions::new().append(true)` | +| Čítať z konzoly | `io::stdin().read_line(&mut buf)` | +| Prompt bez newline | `print!()` + `io::stdout().flush()` | +| JSON: súbor → štruktúra | `fs::read_to_string` + `serde_json::from_str` | +| JSON: štruktúra → súbor | `serde_json::to_string_pretty` + `fs::write` | +| Testovateľný I/O | `trait IOManager` + `impl IOManager for ...` | + +### Vzor: kompletné menu programu + +```rust +use std::io::{self, Write}; +use std::path::PathBuf; + +fn prompt(text: &str) -> String { + print!("{}", text); + io::stdout().flush().unwrap(); + let mut vstup = String::new(); + io::stdin().read_line(&mut vstup).unwrap(); + vstup.trim().to_string() +} + +fn main() { + let cesta = PathBuf::from("data.json"); + let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) + .unwrap_or_default(); + + loop { + println!("\n--- KNIŽNICA ---"); + println!("1. Pridaj knihu"); + println!("2. Vypíš knihy"); + println!("3. Hľadaj"); + println!("4. Koniec"); + + let volba = prompt("Vyber: "); + + match volba.as_str() { + "1" => { + let nazov = prompt("Názov: "); + let autor = prompt("Autor: "); + // ... pridaj knihu + kniznica.uloz_do_suboru(&cesta); + println!("Pridané."); + } + "2" => { + for kniha in &kniznica.knihy { + println!("{}", kniha); + } + } + "3" => { + let vyraz = prompt("Hľadaj: "); + // ... hľadaj + } + "4" => { + println!("Zbohom!"); + break; + } + _ => println!("Neznáma voľba"), + } + } +} +``` + +### Úlohy 10 + +**10a.** Napíš kompletný interaktívny program pre TODO list: +- Pridaj úlohu (prompt názov) +- Označ ako hotovú (prompt index) +- Vypíš všetky +- Ulož do JSON +- Koniec + +**10b.** Napíš program, ktorý prečíta CSV súbor (meno,vek,mesto), sparsuje riadky do Vec štruktúr, a umožní hľadanie podľa mesta. + +**10c.** Implementuj kompletný IOManager trait pre hru Obesenec. Vytvor aj TestIOManager, ktorý automaticky „hrá" hru s predpripravenými písmenami.