Files
JR-priprava-na-skusku/priprava/rust_priprava2.md
2026-02-15 19:53:04 +01:00

24 KiB

Rust — Základy na dril

Každé cvičenie: prečítaj, napíš kód, skompiluj, porovnaj s riešením na konci. Riešenia sú číslované na konci súboru. Nepozeraj dopredu.


Časť A: Result a Option — kedy čo

Result<T, E> = operácia, ktorá môže zlyhať (súbor, parsing, sieť) Option<T> = hodnota, ktorá môže chýbať (hľadanie, konverzia)

A1. Konverzie medzi Result a Option

Napíš, čo vráti každý výraz. Nekompiluj, najprv tipni, potom over.

fn main() {
    let a: Result<i32, &str> = Ok(5);
    let b: Result<i32, &str> = Err("chyba");

    println!("{:?}", a.ok());        // ?
    println!("{:?}", b.ok());        // ?
    println!("{:?}", a.is_ok());     // ?
    println!("{:?}", b.is_ok());     // ?
    println!("{:?}", a.is_err());    // ?
    println!("{:?}", b.is_err());    // ?

    let c: Option<i32> = Some(10);
    let d: Option<i32> = None;

    println!("{:?}", c.ok_or("prazdne"));   // ?
    println!("{:?}", d.ok_or("prazdne"));   // ?
    println!("{:?}", c.is_some());           // ?
    println!("{:?}", d.is_none());           // ?
}

A2. .ok() vs .is_ok() — kedy ktoré

Máš funkciu:

fn nacitaj(cesta: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(cesta)
}

Napíš 3 rôzne funkcie, každá spracuje výsledok inak:

  1. pouzij_ok() — vráti Option<String> (zahoď chybu, zaujíma ťa len hodnota)
  2. pouzij_is_ok() — vráti bool (zaujíma ťa len či sa podarilo)
  3. pouzij_match() — vypíše obsah ak Ok, vypíše chybu ak Err

A3. Reťazenie .ok()

Napíš funkciu parsuj_cislo(text: &str) -> Option<i32> ktorá:

  1. Vezme text
  2. Skúsi ho sparsovať na i32 (text.parse::<i32>() vracia Result)
  3. Vráti Option<i32>

Použi jeden riadok bez match, bez if.

A4. .unwrap_or() a .unwrap_or_default()

Napíš čo vypíše:

fn main() {
    let a: Option<i32> = Some(5);
    let b: Option<i32> = None;
    let c: Result<i32, &str> = Err("zle");

    println!("{}", a.unwrap_or(0));
    println!("{}", b.unwrap_or(0));
    println!("{}", b.unwrap_or_default());
    println!("{}", c.unwrap_or(99));
    println!("{}", c.unwrap_or_default());

    let d: Option<String> = None;
    println!("{:?}", d.unwrap_or_default());
    println!("{:?}", d.unwrap_or("fallback".to_string()));
}

Časť B: Operátor ? (question mark)

B1. Základné použitie

Táto funkcia je napísaná s match. Prepíš ju tak, aby používala ? a bola čo najkratšia:

fn nacitaj_a_parsuj(cesta: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let obsah = match std::fs::read_to_string(cesta) {
        Ok(s) => s,
        Err(e) => return Err(Box::new(e)),
    };
    let cislo = match obsah.trim().parse::<i32>() {
        Ok(n) => n,
        Err(e) => return Err(Box::new(e)),
    };
    Ok(cislo)
}

B2. ? v Option kontexte

Prepíš s operátorom ? (funkcia vracia Option, nie Result):

fn prvy_sused(vektor: &[i32], index: usize) -> Option<i32> {
    if index == 0 {
        return None;
    }
    let predchadzajuci = index.checked_sub(1);
    match predchadzajuci {
        Some(i) => {
            if i < vektor.len() {
                Some(vektor[i])
            } else {
                None
            }
        }
        None => None,
    }
}

B3. .ok()? vzor

Napíš funkciu nacitaj_json(cesta: &str) -> Option<Vec<String>> ktorá:

  1. Prečíta súbor (Result) → ak zlyhá, vráti None
  2. Deserializuje JSON na Vec<String> (Result) → ak zlyhá, vráti None
  3. Vráti Some(vec)

Použi .ok()? vzor. Celá funkcia na 3 riadky (vrátane signatúry).

B4. Kde ? NEMÔŽEŠ použiť

Čo je zle na tomto kóde? Oprav ho (2 rôzne spôsoby):

fn main() {
    let obsah = std::fs::read_to_string("subor.txt")?;
    println!("{}", obsah);
}

Časť C: let...else vs if let vs match

C1. Kedy ktoré — prepíš

Máš 3 verzie toho istého. Pre každú situáciu nižšie vyber najlepší zápis a napíš ho:

Situácia 1: Chceš hodnotu z Option, ak None tak return None z funkcie.

// Verzia A: match
let hodnota = match mozno_hodnota {
    Some(v) => v,
    None => return None,
};

// Verzia B: if let (??? je toto správne?)

// Verzia C: let...else

Napíš verziu B a C. Ktorá je najkratšia?

Situácia 2: Chceš niečo urobiť len ak je Some, inak nič.

// Ktoré je lepšie — if let alebo match?

Napíš obe verzie.

Situácia 3: Chceš rôzne akcie pre Ok a Err.

// Ktoré je lepšie — match, if let, alebo let...else?

Napíš všetky 3, rozhodni sa.

C2. let...else v praxi

Napíš funkciu spracuj(text: &str) -> Option<String> ktorá:

  1. Skúsi sparsovať text na u32. Ak sa nepodarí → vráť None.
  2. Ak číslo > 100, vráť None.
  3. Vráť Some(format!("Číslo: {}", cislo))

Napíš to dvakrát:

  • Raz s let...else
  • Raz bez let...else (s ? alebo match)

C3. if let vs let...else

Vysvetli sám sebe (komentárom v kóde): kedy if let a kedy let...else?

Napíš funkciu najdi_a_vypis(zoznam: &[String], hladany: &str) ktorá:

  • Nájde prvý reťazec obsahujúci hladany
  • Ak nájde → vypíše ho
  • Ak nenájde → vypíše "Nenájdené"

Napíš to s if let. Potom to prepíš s let...else. Ktoré je prirodzenejšie tu?


Časť D: Iterátory vs cykly

D1. for cyklus → iterátor

Prepíš na iterátorový zápis (bez for, bez mut):

fn pocet_kladnych(cisla: &[i32]) -> usize {
    let mut pocet = 0;
    for c in cisla {
        if *c > 0 {
            pocet += 1;
        }
    }
    pocet
}

D2. filter + collect

Napíš funkciu dlhe_slova(slova: &[String], min_dlzka: usize) -> Vec<&String> ktorá vráti referencie na slová dlhšie ako min_dlzka. Jeden riadok.

D3. find

Napíš funkciu najdi_podla_zaciatku(slova: &[String], prefix: &str) -> Option<&String> ktorá vráti prvé slovo začínajúce daným prefixom. Jeden riadok.

D4. map + collect

Prepíš bez for:

fn zdvojnasob(cisla: &[i32]) -> Vec<i32> {
    let mut vysledok = Vec::new();
    for c in cisla {
        vysledok.push(c * 2);
    }
    vysledok
}

D5. position (na remove z Vec)

Napíš funkciu odstran_podla_mena(zoznam: &mut Vec<String>, meno: &str) -> Result<String, ()>:

  • Nájdi index prvku s daným menom
  • Odstráň ho z vektora a vráť ho
  • Ak neexistuje → Err(())

Použi .position() a .remove().

D6. any / all

Bez cyklu napíš:

  1. obsahuje_zaporne(cisla: &[i32]) -> bool — true ak aspoň jedno < 0
  2. vsetky_neprazdne(texty: &[String]) -> bool — true ak žiadny nie je prázdny

D7. fold / sum

Napíš priemer(cisla: &[f64]) -> Option<f64>:

  • Ak je prázdny slice → None
  • Inak vráť priemer

Použi .iter().sum::<f64>() alebo .fold().

D8. enumerate

Napíš funkciu vypis_s_cislami(polozky: &[String]) ktorá vypíše:

1. prvá položka
2. druhá položka

Použi .enumerate().

D9. iter() vs into_iter() vs iter_mut()

Čo je zle na tomto kóde? Oprav:

fn zvys_vsetky(cisla: &mut Vec<i32>) {
    for c in cisla.iter() {
        *c += 1;
    }
}

D10. Reťazenie iterátorov

Napíš jedným výrazom (reťazením): Máš Vec<String> mien. Chceš nájsť počet mien, ktoré začínajú na 'A' a sú dlhšie ako 3 znaky.

fn pocet_dlhych_a_mien(mena: &[String]) -> usize {
    // jeden riadok
}

Časť E: Closures (uzávery / anonymné funkcie)

E1. Syntax

Prepíš map s closure tromi rôznymi zápismi:

let cisla = vec![1, 2, 3];
// 1. plný zápis s typmi
// 2. skrátený zápis
// 3. najkratší možný zápis

E2. Closure vs funkcia

Napíš filter dvoma spôsobmi:

let slova = vec!["ahoj".to_string(), "svet".to_string(), "a".to_string()];
// 1. s closure
let dlhe: Vec<&String> = slova.iter().filter(/* closure */).collect();
// 2. s pomenovanou funkciou
fn je_dlhe(s: &&String) -> bool { /* */ }
let dlhe: Vec<&String> = slova.iter().filter(je_dlhe).collect();

E3. move v closure

Čo je zle? Oprav:

fn vytvor_pozdrav(meno: String) -> impl Fn() -> String {
    let closure = || format!("Ahoj, {}!", meno);
    closure
}

E4. Closure zachytávajúci &mut

Napíš kód, ktorý:

  1. Vec<i32> s hodnotami [1, 2, 3]
  2. Vytvorí closure, ktorý pridá číslo do vektora
  3. Zavolaj closure 3x s hodnotami 4, 5, 6
  4. Vypíš výsledný vektor

Časť F: Kombinované cvičenia (ako na skúške)

F1. Kompletný CRUD s Option/Result

Implementuj (bez pozerania na riešenia predtým):

struct Zamestnanec {
    meno: String,
    plat: u32,
}

struct Firma {
    zamestnanci: Vec<Zamestnanec>,
}

impl Firma {
    fn new() -> Firma { /* */ }

    // Pridaj zamestnanca. Ak meno existuje → Err(())
    fn pridaj(&mut self, z: Zamestnanec) -> Result<(), ()> { /* */ }

    // Odstráň podľa mena. Vráť odstranenéno. Ak neexistuje → Err(())
    fn odstran(&mut self, meno: &str) -> Result<Zamestnanec, ()> { /* */ }

    // Nájdi podľa mena
    fn najdi(&self, meno: &str) -> Option<&Zamestnanec> { /* */ }

    // Všetci s platom nad X
    fn s_platom_nad(&self, min: u32) -> Vec<&Zamestnanec> { /* */ }

    // Priemerný plat. None ak nikto.
    fn priemerny_plat(&self) -> Option<f64> { /* */ }

    // Najdi najlepsie plateneho
    fn najlepsi_plat(&self) -> Option<&Zamestnanec> { /* */ }
}

F2. File I/O kombinácia

Napíš obe funkcie pre Firma (s derive Serialize, Deserialize):

fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option<Firma>
fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool

F3. CLI parsovanie vstupu

Napíš funkciu v main() (bez clap), ktorá:

  1. Prečíta argumenty z príkazového riadku (std::env::args())
  2. Prvý argument je príkaz: "pridaj", "odstran", "zoznam"
  3. Pre "pridaj": ďalšie 2 argumenty sú meno a plat
  4. Pre "odstran": ďalší argument je meno
  5. Pre "zoznam": žiadne ďalšie argumenty

Použi let args: Vec<String> = std::env::args().collect(); a potom match/if na args[1].

F4. Všetko dokopy

Napíš kompletný program (lib.rs + main.rs) pre jednoduchý Slovník (prekladový):

Štruktúra Slovo: original (String), preklad (String), kategoria (String)
Štruktúra Slovnik: slova: Vec<Slovo>
  + Serialize, Deserialize, Default

Metódy:
- nacitaj_zo_suboru(cesta) -> Option<Slovnik>
- uloz_do_suboru(cesta) -> bool
- pridaj_slovo(slovo) -> Result<(), ()>   (duplicita podľa original)
- odstran_slovo(original: &str) -> Result<Slovo, ()>
- preloz(original: &str) -> Option<&str>  (vráti preklad)
- slova_v_kategorii(kat: &str) -> Vec<&Slovo>
- pocet_v_kategoriach() -> HashMap<String, usize>

Časť G: Špeciálne vzory

G1. or_insert / entry API

Napíš funkciu pocitaj_znaky(text: &str) -> HashMap<char, usize>: Počíta výskyt každého znaku v texte. Použi .entry().or_insert(0).

G2. Option::map

Prepíš bez match/if:

fn zdvojnasob_ak_existuje(x: Option<i32>) -> Option<i32> {
    match x {
        Some(n) => Some(n * 2),
        None => None,
    }
}

G3. Option::and_then (flatmap)

Napíš funkciu:

// Máš Option<String>. Ak Some, skús parsovať na i32. Ak sa podarí, vráť Some(i32). Inak None.
fn parsuj_option(maybe_text: Option<String>) -> Option<i32>

Použi .and_then(). Jeden riadok.

G4. Result::map_err

Napíš funkciu:

// Konvertuj io::Error na String
fn nacitaj(cesta: &str) -> Result<String, String> {
    // std::fs::read_to_string vracia Result<String, io::Error>
    // ty chceš Result<String, String>
}

Použi .map_err().

G5. .flatten()

Čo vráti?

let a: Option<Option<i32>> = Some(Some(5));
let b: Option<Option<i32>> = Some(None);
let c: Option<Option<i32>> = None;

println!("{:?}", a.flatten());
println!("{:?}", b.flatten());
println!("{:?}", c.flatten());



RIEŠENIA

A1

Some(5)
None
true
false
false
true
Ok(10)
Err("prazdne")
true
true

A2

fn pouzij_ok(cesta: &str) -> Option<String> {
    nacitaj(cesta).ok()
}

fn pouzij_is_ok(cesta: &str) -> bool {
    nacitaj(cesta).is_ok()
}

fn pouzij_match(cesta: &str) {
    match nacitaj(cesta) {
        Ok(obsah) => println!("{}", obsah),
        Err(e) => println!("Chyba: {}", e),
    }
}

A3

fn parsuj_cislo(text: &str) -> Option<i32> {
    text.parse::<i32>().ok()
}

A4

5
0
0
99
0
""
"fallback"

Pozn.: unwrap_or_default() — pre i32 default je 0, pre String je "".

B1

fn nacitaj_a_parsuj(cesta: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let obsah = std::fs::read_to_string(cesta)?;
    let cislo = obsah.trim().parse::<i32>()?;
    Ok(cislo)
}

B2

fn prvy_sused(vektor: &[i32], index: usize) -> Option<i32> {
    let i = index.checked_sub(1)?;
    Some(*vektor.get(i)?)
}

B3

fn nacitaj_json(cesta: &str) -> Option<Vec<String>> {
    let raw = std::fs::read_to_string(cesta).ok()?;
    serde_json::from_str(&raw).ok()
}

B4

main() vracia (), nie Result. Operátor ? sa nedá použiť vo funkcii, ktorá nevracia Result alebo Option.

Oprava 1 — main vracia Result:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let obsah = std::fs::read_to_string("subor.txt")?;
    println!("{}", obsah);
    Ok(())
}

Oprava 2 — match/unwrap:

fn main() {
    match std::fs::read_to_string("subor.txt") {
        Ok(obsah) => println!("{}", obsah),
        Err(e) => eprintln!("Chyba: {}", e),
    }
}

C1

Situácia 1:

// Verzia B: if let — NEDÁ SA dobre, lebo chceš pokračovať s hodnotou
// if let Some(v) = mozno_hodnota { v } else { return None }
// Toto nie je pekné, lebo v žije len v if bloku.

// Verzia C: let...else
let Some(hodnota) = mozno_hodnota else {
    return None;
};

// Najkratšia: operátor ?
let hodnota = mozno_hodnota?;

Situácia 2:

// if let je lepšie — nechceš else vetvu
if let Some(v) = mozno_hodnota {
    println!("Mám: {}", v);
}

// match je zbytočne dlhý:
match mozno_hodnota {
    Some(v) => println!("Mám: {}", v),
    None => {}  // zbytočný riadok
}

Situácia 3:

// match je najlepší — máš 2 rôzne vetvy
match vysledok {
    Ok(v) => println!("OK: {}", v),
    Err(e) => println!("Chyba: {}", e),
}

// if let — len ak ťa zaujíma jedna vetva:
if let Ok(v) = vysledok {
    println!("OK: {}", v);
}

// let...else — ak chceš hodnotu a pri chybe return:
let Ok(v) = vysledok else {
    println!("Chyba!");
    return;
};
println!("OK: {}", v);

C2

// S let...else:
fn spracuj(text: &str) -> Option<String> {
    let Ok(cislo) = text.parse::<u32>() else {
        return None;
    };
    if cislo > 100 {
        return None;
    }
    Some(format!("Číslo: {}", cislo))
}

// S ? a filter:
fn spracuj(text: &str) -> Option<String> {
    let cislo = text.parse::<u32>().ok()?;
    if cislo > 100 {
        return None;
    }
    Some(format!("Číslo: {}", cislo))
}

C3

// if let — keď chceš NIEČO UROBIŤ s hodnotou, ale nepotrebuješ ju ďalej
// let...else — keď POTREBUJEŠ HODNOTU ďalej v kóde a chceš early return

fn najdi_a_vypis(zoznam: &[String], hladany: &str) {
    // if let je tu prirodzenejšie — netreba hodnotu ďalej:
    if let Some(najdeny) = zoznam.iter().find(|s| s.contains(hladany)) {
        println!("{}", najdeny);
    } else {
        println!("Nenájdené");
    }

    // let...else je tu menej prirodzené:
    let Some(najdeny) = zoznam.iter().find(|s| s.contains(hladany)) else {
        println!("Nenájdené");
        return;
    };
    println!("{}", najdeny);
}

D1

fn pocet_kladnych(cisla: &[i32]) -> usize {
    cisla.iter().filter(|c| **c > 0).count()
}

D2

fn dlhe_slova(slova: &[String], min_dlzka: usize) -> Vec<&String> {
    slova.iter().filter(|s| s.len() > min_dlzka).collect()
}

D3

fn najdi_podla_zaciatku<'a>(slova: &'a [String], prefix: &str) -> Option<&'a String> {
    slova.iter().find(|s| s.starts_with(prefix))
}

D4

fn zdvojnasob(cisla: &[i32]) -> Vec<i32> {
    cisla.iter().map(|c| c * 2).collect()
}

D5

fn odstran_podla_mena(zoznam: &mut Vec<String>, meno: &str) -> Result<String, ()> {
    let pos = zoznam.iter().position(|s| s == meno).ok_or(())?;
    Ok(zoznam.remove(pos))
}

Alternatíva bez ?:

fn odstran_podla_mena(zoznam: &mut Vec<String>, meno: &str) -> Result<String, ()> {
    if let Some(pos) = zoznam.iter().position(|s| s == meno) {
        Ok(zoznam.remove(pos))
    } else {
        Err(())
    }
}

D6

fn obsahuje_zaporne(cisla: &[i32]) -> bool {
    cisla.iter().any(|c| *c < 0)
}

fn vsetky_neprazdne(texty: &[String]) -> bool {
    texty.iter().all(|t| !t.is_empty())
}

D7

fn priemer(cisla: &[f64]) -> Option<f64> {
    if cisla.is_empty() {
        return None;
    }
    Some(cisla.iter().sum::<f64>() / cisla.len() as f64)
}

D8

fn vypis_s_cislami(polozky: &[String]) {
    for (i, polozka) in polozky.iter().enumerate() {
        println!("{}. {}", i + 1, polozka);
    }
}

D9

iter() dáva &i32 (nemeniteľné referencie). Treba iter_mut():

fn zvys_vsetky(cisla: &mut Vec<i32>) {
    for c in cisla.iter_mut() {
        *c += 1;
    }
}

D10

fn pocet_dlhych_a_mien(mena: &[String]) -> usize {
    mena.iter().filter(|m| m.starts_with('A') && m.len() > 3).count()
}

E1

let cisla = vec![1, 2, 3];
// 1. plný
let a: Vec<i32> = cisla.iter().map(|x: &i32| -> i32 { x * 2 }).collect();
// 2. skrátený
let b: Vec<i32> = cisla.iter().map(|x| x * 2).collect();
// 3. najkratší — tu sa nedá skrátiť viac, ale pri jednoduchej operácii:
let c: Vec<i32> = cisla.iter().map(|x| x * 2).collect();

E2

let slova = vec!["ahoj".to_string(), "svet".to_string(), "a".to_string()];

// 1. closure
let dlhe: Vec<&String> = slova.iter().filter(|s| s.len() > 1).collect();

// 2. funkcia
fn je_dlhe(s: &&String) -> bool {
    s.len() > 1
}
let dlhe: Vec<&String> = slova.iter().filter(je_dlhe).collect();

E3

Treba move — closure prežije funkciu, ale meno je lokálna premenná:

fn vytvor_pozdrav(meno: String) -> impl Fn() -> String {
    move || format!("Ahoj, {}!", meno)
}

E4

fn main() {
    let mut cisla = vec![1, 2, 3];
    let mut pridaj = |x: i32| cisla.push(x);
    pridaj(4);
    pridaj(5);
    pridaj(6);
    // pozor: println! nemôže byť pred posledným volaním pridaj
    // lebo closure drží &mut cisla
    println!("{:?}", cisla); // [1, 2, 3, 4, 5, 6]
}

Pozn.: Ak by si chcel println! medzi volaniami, musel by si dropiť closure alebo použiť iný prístup.

F1

struct Zamestnanec {
    meno: String,
    plat: u32,
}

struct Firma {
    zamestnanci: Vec<Zamestnanec>,
}

impl Firma {
    fn new() -> Firma {
        Firma { zamestnanci: Vec::new() }
    }

    fn pridaj(&mut self, z: Zamestnanec) -> Result<(), ()> {
        if self.zamestnanci.iter().any(|x| x.meno == z.meno) {
            return Err(());
        }
        self.zamestnanci.push(z);
        Ok(())
    }

    fn odstran(&mut self, meno: &str) -> Result<Zamestnanec, ()> {
        let pos = self.zamestnanci.iter().position(|z| z.meno == meno).ok_or(())?;
        Ok(self.zamestnanci.remove(pos))
    }

    fn najdi(&self, meno: &str) -> Option<&Zamestnanec> {
        self.zamestnanci.iter().find(|z| z.meno == meno)
    }

    fn s_platom_nad(&self, min: u32) -> Vec<&Zamestnanec> {
        self.zamestnanci.iter().filter(|z| z.plat > min).collect()
    }

    fn priemerny_plat(&self) -> Option<f64> {
        if self.zamestnanci.is_empty() {
            return None;
        }
        let sucet: u32 = self.zamestnanci.iter().map(|z| z.plat).sum();
        Some(sucet as f64 / self.zamestnanci.len() as f64)
    }

    fn najlepsi_plat(&self) -> Option<&Zamestnanec> {
        self.zamestnanci.iter().max_by_key(|z| z.plat)
    }
}

F2

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Zamestnanec { meno: String, plat: u32 }

#[derive(Serialize, Deserialize, Default)]
struct Firma { zamestnanci: Vec<Zamestnanec> }

impl Firma {
    fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option<Firma> {
        let raw = std::fs::read_to_string(cesta).ok()?;
        serde_json::from_str(&raw).ok()
    }

    fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool {
        let Ok(json) = serde_json::to_string_pretty(&self) else {
            return false;
        };
        std::fs::write(cesta, json).is_ok()
    }
}

F3

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        eprintln!("Použi: pridaj <meno> <plat> | odstran <meno> | zoznam");
        return;
    }
    match args[1].as_str() {
        "pridaj" => {
            if args.len() < 4 {
                eprintln!("Použi: pridaj <meno> <plat>");
                return;
            }
            let meno = &args[2];
            let plat: u32 = match args[3].parse() {
                Ok(p) => p,
                Err(_) => { eprintln!("Neplatný plat"); return; }
            };
            println!("Pridávam: {} s platom {}", meno, plat);
        }
        "odstran" => {
            if args.len() < 3 {
                eprintln!("Použi: odstran <meno>");
                return;
            }
            println!("Odstraňujem: {}", args[2]);
        }
        "zoznam" => {
            println!("Zobrazujem zoznam...");
        }
        iny => eprintln!("Neznámy príkaz: {}", iny),
    }
}

F4

// lib.rs
use serde::{Serialize, Deserialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Clone)]
pub struct Slovo {
    pub original: String,
    pub preklad: String,
    pub kategoria: String,
}

#[derive(Serialize, Deserialize, Default)]
pub struct Slovnik {
    pub slova: Vec<Slovo>,
}

impl Slovnik {
    pub fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option<Slovnik> {
        let raw = std::fs::read_to_string(cesta).ok()?;
        serde_json::from_str(&raw).ok()
    }

    pub fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool {
        let Ok(json) = serde_json::to_string_pretty(&self) else {
            return false;
        };
        std::fs::write(cesta, json).is_ok()
    }

    pub fn pridaj_slovo(&mut self, slovo: Slovo) -> Result<(), ()> {
        if self.slova.iter().any(|s| s.original == slovo.original) {
            return Err(());
        }
        self.slova.push(slovo);
        Ok(())
    }

    pub fn odstran_slovo(&mut self, original: &str) -> Result<Slovo, ()> {
        let pos = self.slova.iter().position(|s| s.original == original).ok_or(())?;
        Ok(self.slova.remove(pos))
    }

    pub fn preloz(&self, original: &str) -> Option<&str> {
        self.slova.iter()
            .find(|s| s.original == original)
            .map(|s| s.preklad.as_str())
    }

    pub fn slova_v_kategorii(&self, kat: &str) -> Vec<&Slovo> {
        self.slova.iter().filter(|s| s.kategoria == kat).collect()
    }

    pub fn pocet_v_kategoriach(&self) -> HashMap<String, usize> {
        let mut mapa = HashMap::new();
        for slovo in &self.slova {
            *mapa.entry(slovo.kategoria.clone()).or_insert(0) += 1;
        }
        mapa
    }
}

G1

use std::collections::HashMap;

fn pocitaj_znaky(text: &str) -> HashMap<char, usize> {
    let mut mapa = HashMap::new();
    for c in text.chars() {
        *mapa.entry(c).or_insert(0) += 1;
    }
    mapa
}

G2

fn zdvojnasob_ak_existuje(x: Option<i32>) -> Option<i32> {
    x.map(|n| n * 2)
}

G3

fn parsuj_option(maybe_text: Option<String>) -> Option<i32> {
    maybe_text.and_then(|t| t.parse::<i32>().ok())
}

G4

fn nacitaj(cesta: &str) -> Result<String, String> {
    std::fs::read_to_string(cesta).map_err(|e| e.to_string())
}

G5

Some(5)
None
None