Files
JR-priprava-na-skusku/priprava/rust_priprava3.md
2026-02-16 19:50:35 +01:00

28 KiB
Raw Permalink Blame History

Rust — Iterátory, Closures, Kombinácie (DG)

Pre každú tému: vysvetleniepríkladyúlohy na precvičenie → riešenia na konci.


Časť D: Iterátory

D.1 — Čo je iterátor

Iterátor je spôsob, ako prejsť cez kolekciu prvok po prvku. V Ruste existujú 3 spôsoby ako získať iterátor z kolekcie:

Metóda Dáva ti Kedy použiť
.iter() &T (nemeniteľná referencia) Chceš len čítať
.iter_mut() &mut T (meniteľná referencia) Chceš meniť prvky na mieste
.into_iter() T (vlastníctvo) Chceš konzumovať kolekciu
let mut cisla = vec![1, 2, 3];

// iter() — len čítam, cisla existujú ďalej
for c in cisla.iter() {
    println!("{}", c);  // c je &i32
}

// iter_mut() — mením prvky
for c in cisla.iter_mut() {
    *c += 10;  // c je &mut i32, treba dereferencovať
}
// cisla je teraz [11, 12, 13]

// into_iter() — preberám vlastníctvo, cisla po tomto neexistujú
let cisla2 = vec![1, 2, 3];
for c in cisla2.into_iter() {
    println!("{}", c);  // c je i32
}
// cisla2 tu už nemôžeš použiť!

Pravidlo: Ak máš &self metódu (len čítaš), použi .iter(). Ak máš &mut self, použi .iter_mut().


D.2 — filter

Čo robí: Prejde cez iterátor a nechá len prvky, pre ktoré closure vráti true.

Typ: .filter() dáva referencie na referencie. Ak iteruješ cez &T, vo filtri dostaneš &&T.

let cisla = vec![1, 2, 3, 4, 5, 6];

// Nechaj len párne
let parne: Vec<&i32> = cisla.iter().filter(|c| *c % 2 == 0).collect();
// parne = [&2, &4, &6]

// Ak chceš Vec<i32> (nie referencie), použi .copied() alebo .cloned()
let parne: Vec<i32> = cisla.iter().filter(|c| *c % 2 == 0).copied().collect();
// parne = [2, 4, 6]

So štruktúrami:

struct Kniha { nazov: String, rok: u16 }

let knihy = vec![
    Kniha { nazov: "A".into(), rok: 2020 },
    Kniha { nazov: "B".into(), rok: 2015 },
];

// Knihy po roku 2018
let nove: Vec<&Kniha> = knihy.iter().filter(|k| k.rok > 2018).collect();

Na skúške: Každá metóda typu daj_knihy_podla_X je filter + collect.


D.3 — find

Čo robí: Vráti prvý prvok, pre ktorý closure vráti true. Vráti Option.

let cisla = vec![1, 2, 3, 4, 5];

let prve_parne: Option<&i32> = cisla.iter().find(|c| *c % 2 == 0);
// Some(&2)

let viac_ako_10: Option<&i32> = cisla.iter().find(|c| **c > 10);
// None

So štruktúrami:

let knihy = vec![
    Kniha { nazov: "Hobbit".into(), rok: 1937 },
    Kniha { nazov: "Duna".into(), rok: 1965 },
];

// Nájdi knihu podľa názvu
let najdena: Option<&Kniha> = knihy.iter().find(|k| k.nazov == "Duna");

Na skúške: Každá metóda typu daj_knihu_podla_isbn je find.


D.4 — position

Čo robí: Ako find, ale vráti index namiesto hodnoty. Vráti Option<usize>.

let mena = vec!["Anna".to_string(), "Boris".to_string(), "Cyril".to_string()];

let index: Option<usize> = mena.iter().position(|m| m == "Boris");
// Some(1)

Hlavné použitie — odstránenie z Vec:

fn odstran(zoznam: &mut Vec<String>, meno: &str) -> Result<String, ()> {
    // 1. nájdi index
    let pos = zoznam.iter().position(|s| s == meno).ok_or(())?;
    // 2. odstráň na tom indexe (remove posunie zvyšné prvky)
    Ok(zoznam.remove(pos))
}

Na skúške: Každá metóda odstran_knihu, odstran_kontakt atď. je position + remove.


D.5 — map

Čo robí: Transformuje každý prvok. [A, B, C][f(A), f(B), f(C)].

let cisla = vec![1, 2, 3];

// Zdvojnásob každé
let dvojnasobok: Vec<i32> = cisla.iter().map(|c| c * 2).collect();
// [2, 4, 6]

// Prevod na String
let texty: Vec<String> = cisla.iter().map(|c| c.to_string()).collect();
// ["1", "2", "3"]

// Vytiahni pole zo štruktúry
let nazvy: Vec<&String> = knihy.iter().map(|k| &k.nazov).collect();

D.6 — any a all

Čo robia: Vracajú bool.

let cisla = vec![1, -2, 3, -4, 5];

// any — existuje aspoň jeden, pre ktorý platí?
let ma_zaporne: bool = cisla.iter().any(|c| *c < 0);
// true

// all — platí pre VŠETKY?
let vsetky_kladne: bool = cisla.iter().all(|c| *c > 0);
// false

D.7 — sum a count

let cisla = vec![1, 2, 3, 4, 5];

// Súčet — musíš špecifikovať typ
let sucet: i32 = cisla.iter().sum();
// alebo
let sucet = cisla.iter().sum::<i32>();
// 15

// Počet prvkov spĺňajúcich podmienku
let pocet_parnych: usize = cisla.iter().filter(|c| *c % 2 == 0).count();
// 2

D.8 — max_by_key a min_by_key

Čo robí: Nájde maximum/minimum podľa kľúča. Vráti Option.

struct Zamestnanec { meno: String, plat: u32 }

let zamestnanci = vec![
    Zamestnanec { meno: "Anna".into(), plat: 3000 },
    Zamestnanec { meno: "Boris".into(), plat: 4500 },
    Zamestnanec { meno: "Cyril".into(), plat: 2800 },
];

let najbohatsi: Option<&Zamestnanec> = zamestnanci.iter().max_by_key(|z| z.plat);
// Some(Boris)

D.9 — enumerate

Čo robí: Pridá index ku každému prvku. [A, B, C][(0, A), (1, B), (2, C)].

let mena = vec!["Anna", "Boris", "Cyril"];

for (i, meno) in mena.iter().enumerate() {
    println!("{}. {}", i + 1, meno);
}
// 1. Anna
// 2. Boris
// 3. Cyril

D.10 — Reťazenie

Iterátory sa dajú reťaziť. Každá metóda vráti nový iterátor:

let zamestnanci = vec![/* ... */];

// Mená zamestnancov s platom nad 3000, zoradené
let bohati_mena: Vec<&String> = zamestnanci.iter()
    .filter(|z| z.plat > 3000)       // nechaj len bohatých
    .map(|z| &z.meno)                // vytiahni meno
    .collect();                       // zhromaždi do Vec

Kedy for, kedy iterátor:

  • Jednoduchý prechod s výpisom → for
  • Filtrovanie → .filter().collect()
  • Transformácia → .map().collect()
  • Hľadanie → .find() alebo .position()
  • Podmienka → .any() alebo .all()
  • Počítanie → .filter().count()
  • Ak robíš kombináciu hore → reťaz iterátorov

Úlohy D

D-1. Máš Vec<i32> = [3, -1, 4, -1, 5, 9, -2, 6]. Napíš jedným výrazom (bez for):

  • a) Počet kladných čísel
  • b) Súčet záporných čísel
  • c) Vec len kladných čísel (ako Vec<i32>, nie referencie)
  • d) Či sú všetky čísla väčšie ako -5
  • e) Či existuje číslo väčšie ako 8

D-2. Máš:

struct Student { meno: String, vek: u8, priemer: f32 }
let studenti: Vec<Student> = /* ... */;

Napíš (každé na jeden riadok):

  • a) Mená študentov s priemerom pod 2.0 → Vec<&String>
  • b) Najstarší študent → Option<&Student>
  • c) Priemerný vek všetkých študentov → Option<f64>
  • d) Existuje študent mladší ako 18? → bool
  • e) Odstráň študenta podľa mena z &mut Vec<Student>Result<Student, ()>

D-3. Prepíš na iterátorový zápis bez for a bez mut premenných:

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

D-4. Čo je zle? Oprav:

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

Časť E: Closures (uzávery)

E.1 — Čo je closure

Closure je anonymná funkcia, ktorá môže zachytiť premenné z okolitého prostredia.

// Normálna funkcia — nemá prístup k okoliu
fn pricti_5(x: i32) -> i32 {
    x + 5
}

// Closure — zachytí premennú `zaklad` z okolia
let zaklad = 5;
let pricti = |x: i32| -> i32 { x + zaklad };

println!("{}", pricti(10)); // 15

E.2 — Syntax closure

Od najdlhšieho po najkratšie:

// Plný zápis (ako funkcia, ale s ||)
let f = |x: i32, y: i32| -> i32 { x + y };

// Bez typov (Rust si ich odvodí)
let f = |x, y| { x + y };

// Bez zátvoriek (ak je len jeden výraz)
let f = |x, y| x + y;

// Bez parametrov
let f = || println!("ahoj");

// S jedným parametrom
let f = |x| x * 2;

Pravidlo: V .filter(), .map(), .find() atď. vždy píš najkratší zápis.

E.3 — Closures v iterátoroch

Toto sú najčastejšie situácie:

let cisla = vec![1, 2, 3, 4, 5];

// filter — closure berie &&T (referenciu na referenciu)
cisla.iter().filter(|c| **c > 3)     // c je &&i32, treba **
cisla.iter().filter(|&c| *c > 3)     // destructuring v parametri
cisla.iter().filter(|&&c| c > 3)     // plný destructuring

// map — closure berie &T
cisla.iter().map(|c| c * 2)          // c je &i32, Rust automaticky dereferencuje pri *

// find — rovnako ako filter
cisla.iter().find(|c| **c == 3)

// any/all — rovnako ako filter
cisla.iter().any(|c| *c > 3)         // tu je len &T, nie &&T!

Prečo filter má &&T? Lebo .iter() dáva &T, a filter ešte obalí ďalšou referenciou. Ale Rust je v praxi dosť chytrý — väčšinou stačí |c| c > &3 alebo |c| *c > 3.

E.4 — move closure

Normálne closure zachytáva referencie. move prenesie vlastníctvo do closure:

// BEZ move — closure zachytí &meno
let meno = "Anna".to_string();
let pozdrav = || println!("Ahoj {}", meno);
pozdrav();
println!("{}", meno); // OK, meno stále existuje

// S move — closure prevezme vlastníctvo mena
let meno = "Anna".to_string();
let pozdrav = move || println!("Ahoj {}", meno);
pozdrav();
// println!("{}", meno); // CHYBA! meno bolo presunuté do closure

Kedy treba move: Keď closure musí prežiť pôvodnú premennú (napr. vrátenie closure z funkcie).

fn vytvor_pozdrav(meno: String) -> impl Fn() {
    // BEZ move: CHYBA — meno zomrie na konci funkcie, ale closure ho referencuje
    // S move: OK — closure vlastní meno
    move || println!("Ahoj {}", meno)
}

E.5 — Closure zachytávajúci &mut

Ak closure modifikuje premennú, zachytí ju ako &mut:

let mut pocitadlo = 0;
let mut zvys = || { pocitadlo += 1; };
zvys();
zvys();
zvys();
println!("{}", pocitadlo); // 3

Dôležité: Kým existuje &mut closure, nemôžeš čítať premennú inak:

let mut cisla = vec![1, 2, 3];
let mut pridaj = |x| cisla.push(x);
pridaj(4);
// println!("{:?}", cisla); // CHYBA TU! pridaj stále drží &mut cisla
pridaj(5);
drop(pridaj); // explicitne uvoľni closure
println!("{:?}", cisla); // OK teraz — [1, 2, 3, 4, 5]

Alebo jednoducho — posledné použitie closure pred prvým iným použitím:

let mut cisla = vec![1, 2, 3];
let mut pridaj = |x| cisla.push(x);
pridaj(4);
pridaj(5); // posledné volanie
println!("{:?}", cisla); // OK — Rust vie, že pridaj sa už nepoužije

Úlohy E

E-1. Napíš 3 verzie tej istej operácie (vyfiltruj čísla > 10 z Vec):

  • a) S pomenovanou funkciou (nie closure)
  • b) S plným zápisom closure (aj typy)
  • c) S najkratším zápisom

E-2. Čo je zle? Oprav:

fn vrat_nasobicku(faktor: i32) -> impl Fn(i32) -> i32 {
    |x| x * faktor
}

E-3. Napíš funkciu aplikuj_na_vsetky(cisla: &[i32], f: impl Fn(i32) -> i32) -> Vec<i32> ktorá aplikuje funkciu f na každý prvok. Potom ju zavolaj s:

  • a) closure, ktorý zdvojnásobí
  • b) closure, ktorý pripočíta 100
  • c) pomenovanou funkciou, ktorá vráti absolútnu hodnotu

E-4. Máš Vec<String>. Napíš closure, ktorý:

  • Zachytí &mut Vec<String>
  • Pridá reťazec do vektora
  • Zavolaj ho 3x, potom vypíš výsledok

Časť F: HashMap

F.1 — Základy

use std::collections::HashMap;

let mut mapa: HashMap<String, i32> = HashMap::new();

// Vloženie
mapa.insert("Anna".to_string(), 25);
mapa.insert("Boris".to_string(), 30);

// Čítanie — vráti Option<&V>
let vek: Option<&i32> = mapa.get("Anna");   // Some(&25)
let vek: Option<&i32> = mapa.get("Cyril");  // None

// Kontrola existencie
let existuje: bool = mapa.contains_key("Anna"); // true

// Iterácia
for (meno, vek) in &mapa {
    println!("{}: {}", meno, vek);
}

F.2 — Entry API (dôležité na skúške!)

entry() je spôsob, ako pracovať s kľúčom, ktorý môže alebo nemusí existovať:

use std::collections::HashMap;

let mut pocty: HashMap<String, usize> = HashMap::new();
let slova = vec!["ahoj", "svet", "ahoj", "rust", "svet", "ahoj"];

// Počítaj výskyty
for slovo in &slova {
    // entry() vráti Entry — buď Occupied alebo Vacant
    // or_insert(0) — ak kľúč neexistuje, vlož 0
    // vráti &mut hodnotu
    *pocty.entry(slovo.to_string()).or_insert(0) += 1;
}
// pocty = {"ahoj": 3, "svet": 2, "rust": 1}

Rozklad:

*pocty.entry(slovo.to_string()).or_insert(0) += 1;
//     ^^^^^ nájdi alebo vytvor kľúč
//                                  ^^^^^^^^^^^^ ak neexistuje, vlož 0
//                                               ^^^^ vráti &mut usize
// ^^^                                                 += 1  dereferencuj a zvýš

Na skúške: Metódy ako vypis_vydavatelstva_a_pocet_knih alebo pocet_v_kategoriach vždy používajú tento vzor.

F.3 — HashMap zo štruktúr

Typický vzor na skúške — zoskup podľa poľa a spočítaj:

struct Kniha { nazov: String, vydavatelstvo: String }

fn pocty_podla_vydavatelstva(knihy: &[Kniha]) -> HashMap<String, usize> {
    let mut mapa = HashMap::new();
    for kniha in knihy {
        *mapa.entry(kniha.vydavatelstvo.clone()).or_insert(0) += 1;
    }
    mapa
}

// Výpis:
fn vypis_statistiky(knihy: &[Kniha]) {
    let pocty = pocty_podla_vydavatelstva(knihy);
    for (vydavatelstvo, pocet) in &pocty {
        println!("{}: {}", vydavatelstvo, pocet);
    }
}

F.4 — or_insert_with a or_default

// or_insert_with — lazy verzia, closure sa zavolá len ak kľúč neexistuje
mapa.entry(kluc).or_insert_with(|| Vec::new());

// or_default — použije Default trait
// Pre Vec → prázdny vektor, pre usize → 0, pre String → ""
mapa.entry(kluc).or_default();

// Príklad: zoskupenie kníh podľa žánru
let mut podla_zanru: HashMap<String, Vec<&Kniha>> = HashMap::new();
for kniha in &knihy {
    podla_zanru.entry(kniha.zaner.clone()).or_default().push(kniha);
}

Úlohy F

F-1. Napíš funkciu pocitaj_znaky(text: &str) -> HashMap<char, usize> — počet výskytov každého znaku.

F-2. Máš Vec<Student> kde Student má meno, trieda: String, priemer: f32. Napíš:

  • a) pocet_v_triedach(studenti: &[Student]) -> HashMap<String, usize> — koľko študentov v každej triede
  • b) najlepsi_v_triede(studenti: &[Student]) -> HashMap<String, &Student> — študent s najnižším priemerom v každej triede

F-3. Napíš funkciu invertuj_mapu(mapa: &HashMap<String, String>) -> HashMap<String, String> — otočí kľúče a hodnoty.


Časť G: Option/Result kombinátory

G.1 — .map() na Option

Čo robí: Ak je Some(x), aplikuje funkciu na x. Ak None, zostane None.

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

let a2: Option<i32> = a.map(|x| x * 2);     // Some(10)
let b2: Option<i32> = b.map(|x| x * 2);     // None

// Praktické — vytiahni pole zo Option<&Struct>
let kniha: Option<&Kniha> = najdi_knihu("Duna");
let nazov: Option<&String> = kniha.map(|k| &k.nazov);

// Prevod typu
let cislo: Option<i32> = Some(42);
let text: Option<String> = cislo.map(|c| c.to_string()); // Some("42")

Nahrádza:

// Toto:
match opt {
    Some(x) => Some(f(x)),
    None => None,
}
// Je to isté ako:
opt.map(f)

G.2 — .and_then() (flatmap)

Čo robí: Ako .map(), ale closure vracia Option. Zabraňuje Option<Option<T>>.

// map by vytvorilo Option<Option<i32>>:
let text: Option<String> = Some("42".to_string());
let zle: Option<Option<i32>> = text.map(|t| t.parse::<i32>().ok());

// and_then "sploští":
let text: Option<String> = Some("42".to_string());
let dobre: Option<i32> = text.and_then(|t| t.parse::<i32>().ok());
// Some(42)

let text2: Option<String> = Some("abc".to_string());
let dobre2: Option<i32> = text2.and_then(|t| t.parse::<i32>().ok());
// None (parse zlyhalo)

Pravidlo: Ak tvoja closure vracia Option → použi .and_then(). Ak vracia holú hodnotu → použi .map().

G.3 — .map_err()

Čo robí: Transformuje chybu v Result, hodnotu necháva.

// read_to_string vracia Result<String, io::Error>
// Ty chceš Result<String, String>
fn nacitaj(cesta: &str) -> Result<String, String> {
    std::fs::read_to_string(cesta)
        .map_err(|e| format!("Chyba čítania: {}", e))
}

G.4 — .flatten()

Čo robí: Option<Option<T>>Option<T>, Result<Result<T, E>, E>Result<T, E>.

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

a.flatten()  // Some(5)
b.flatten()  // None
c.flatten()  // None

G.5 — .unwrap_or(), .unwrap_or_default(), .unwrap_or_else()

let a: Option<i32> = None;

a.unwrap_or(0)            // 0 — vždy vyhodnotí fallback
a.unwrap_or_default()     // 0 — použije Default trait (i32 default = 0)
a.unwrap_or_else(|| {     // lazy — closure sa zavolá len ak None
    println!("Počítam fallback...");
    vypocitaj_nieco()
})

Kedy ktorý:

  • .unwrap_or(hodnota) — fallback je jednoduchá konštanta
  • .unwrap_or_default() — chceš default pre daný typ (0, "", Vec::new(), false)
  • .unwrap_or_else(|| ...) — fallback je drahý výpočet

Úlohy G

G-1. Prepíš bez match:

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

G-2. Prepíš bez match:

fn parsuj_prvy_znak(text: Option<String>) -> Option<char> {
    match text {
        Some(t) => {
            match t.chars().next() {
                Some(c) => Some(c),
                None => None,
            }
        }
        None => None,
    }
}

G-3. Napíš funkciu bezpecne_delenie(a: f64, b: f64) -> Option<f64> — vráti None ak b == 0.0. Bez if, použi .filter() alebo podmienku v .and_then().

Potom napíš: delenie_s_fallbackom(a: f64, b: f64) -> f64 — ak sa nedá deliť, vráti 0.0. Použi predošlú funkciu + .unwrap_or().

G-4. Máš:

fn najdi_pouzivatela(id: u32) -> Option<String> { /* ... */ }
fn najdi_email(meno: &str) -> Option<String> { /* ... */ }

Napíš funkciu email_pre_id(id: u32) -> Option<String> — nájdi použivateľa podľa id, potom jeho email. Jeden riadok s .and_then().

G-5. Čo vypíše? Najprv tipni, potom over:

let a: Option<i32> = Some(5);
println!("{:?}", a.map(|x| x > 3));
println!("{:?}", a.filter(|x| *x > 3));
println!("{:?}", a.filter(|x| *x > 10));
println!("{:?}", a.map(|x| x.to_string()));
println!("{:?}", a.and_then(|x| if x > 3 { Some(x * 2) } else { None }));

Časť H: Kombinované cvičenia (simulácia skúšky)

H-1. Kompletný CRUD

Implementuj bez pozerania na čokoľvek:

use serde::{Serialize, Deserialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Clone)]
struct Produkt {
    nazov: String,
    cena: f64,
    kategoria: String,
    na_sklade: bool,
}

#[derive(Serialize, Deserialize, Default)]
struct Obchod {
    produkty: Vec<Produkt>,
}

impl Obchod {
    fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option<Obchod> { todo!() }
    fn uloz_do_suboru(&self, cesta: &std::path::PathBuf) -> bool { todo!() }

    fn pridaj(&mut self, produkt: Produkt) -> Result<(), ()> { todo!() }
    // duplicita podľa nazvu

    fn odstran(&mut self, nazov: &str) -> Result<Produkt, ()> { todo!() }

    fn najdi(&self, nazov: &str) -> Option<&Produkt> { todo!() }

    fn na_sklade(&self) -> Vec<&Produkt> { todo!() }

    fn v_kategorii(&self, kat: &str) -> Vec<&Produkt> { todo!() }

    fn najdrahsi(&self) -> Option<&Produkt> { todo!() }

    fn priemerny_cena_v_kategorii(&self, kat: &str) -> Option<f64> { todo!() }

    fn pocty_kategorii(&self) -> HashMap<String, usize> { todo!() }
}

H-2. Display enum + struct

use std::fmt;

enum Velkost { Mala, Stredna, Velka }
// Implementuj Display: "S", "M", "L"

struct Tricko { farba: String, velkost: Velkost, cena: f64 }
// Implementuj Display: "Tričko [M] červené - 19.99€"



RIEŠENIA

D-1

let cisla = vec![3, -1, 4, -1, 5, 9, -2, 6];

// a)
let pocet_kladnych: usize = cisla.iter().filter(|c| **c > 0).count();
// 5

// b)
let sucet_zapornych: i32 = cisla.iter().filter(|c| **c < 0).sum();
// -4

// c)
let kladne: Vec<i32> = cisla.iter().filter(|c| **c > 0).copied().collect();
// [3, 4, 5, 9, 6]

// d)
let vsetky_nad_minus5: bool = cisla.iter().all(|c| *c > -5);
// true

// e)
let existuje_nad_8: bool = cisla.iter().any(|c| *c > 8);
// true

D-2

struct Student { meno: String, vek: u8, priemer: f32 }

// a)
let mena: Vec<&String> = studenti.iter().filter(|s| s.priemer < 2.0).map(|s| &s.meno).collect();

// b)
let najstarsi: Option<&Student> = studenti.iter().max_by_key(|s| s.vek);

// c)
fn priemerny_vek(studenti: &[Student]) -> Option<f64> {
    if studenti.is_empty() { return None; }
    let sucet: u32 = studenti.iter().map(|s| s.vek as u32).sum();
    Some(sucet as f64 / studenti.len() as f64)
}

// d)
let ma_mladsiho: bool = studenti.iter().any(|s| s.vek < 18);

// e)
fn odstran_studenta(studenti: &mut Vec<Student>, meno: &str) -> Result<Student, ()> {
    let pos = studenti.iter().position(|s| s.meno == meno).ok_or(())?;
    Ok(studenti.remove(pos))
}

D-3

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

D-4

iter() dáva &i32 (nemeniteľná referencia). Nemôžeš cez ňu meniť. Treba iter_mut():

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

E-1

let cisla = vec![1, 5, 12, 3, 15, 8];

// a) pomenovaná funkcia
fn je_viac_ako_10(c: &&i32) -> bool { **c > 10 }
let vysledok: Vec<&i32> = cisla.iter().filter(je_viac_ako_10).collect();

// b) plný zápis
let vysledok: Vec<&i32> = cisla.iter().filter(|c: &&i32| -> bool { **c > 10 }).collect();

// c) najkratší
let vysledok: Vec<&i32> = cisla.iter().filter(|c| **c > 10).collect();

E-2

Treba movefaktor je lokálna premenná, closure ho musí vlastniť:

fn vrat_nasobicku(faktor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * faktor
}

E-3

fn aplikuj_na_vsetky(cisla: &[i32], f: impl Fn(i32) -> i32) -> Vec<i32> {
    cisla.iter().map(|c| f(*c)).collect()
}

fn main() {
    let cisla = vec![1, -2, 3, -4, 5];

    // a)
    let a = aplikuj_na_vsetky(&cisla, |x| x * 2);

    // b)
    let b = aplikuj_na_vsetky(&cisla, |x| x + 100);

    // c)
    fn absolutna(x: i32) -> i32 { x.abs() }
    let c = aplikuj_na_vsetky(&cisla, absolutna);
}

E-4

fn main() {
    let mut zoznam = Vec::new();
    let mut pridaj = |s: &str| zoznam.push(s.to_string());
    pridaj("ahoj");
    pridaj("svet");
    pridaj("rust");
    println!("{:?}", zoznam); // ["ahoj", "svet", "rust"]
}

F-1

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
}

F-2

struct Student { meno: String, trieda: String, priemer: f32 }

// a)
fn pocet_v_triedach(studenti: &[Student]) -> HashMap<String, usize> {
    let mut mapa = HashMap::new();
    for s in studenti {
        *mapa.entry(s.trieda.clone()).or_insert(0) += 1;
    }
    mapa
}

// b)
fn najlepsi_v_triede<'a>(studenti: &'a [Student]) -> HashMap<String, &'a Student> {
    let mut mapa: HashMap<String, &Student> = HashMap::new();
    for s in studenti {
        mapa.entry(s.trieda.clone())
            .and_modify(|najlepsi| {
                if s.priemer < najlepsi.priemer {
                    *najlepsi = s;
                }
            })
            .or_insert(s);
    }
    mapa
}

F-3

fn invertuj_mapu(mapa: &HashMap<String, String>) -> HashMap<String, String> {
    let mut nova = HashMap::new();
    for (k, v) in mapa {
        nova.insert(v.clone(), k.clone());
    }
    nova
}

G-1

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

G-2

fn parsuj_prvy_znak(text: Option<String>) -> Option<char> {
    text.and_then(|t| t.chars().next())
}

G-3

fn bezpecne_delenie(a: f64, b: f64) -> Option<f64> {
    Some(b).filter(|b| *b != 0.0).map(|b| a / b)
}

fn delenie_s_fallbackom(a: f64, b: f64) -> f64 {
    bezpecne_delenie(a, b).unwrap_or(0.0)
}

G-4

fn email_pre_id(id: u32) -> Option<String> {
    najdi_pouzivatela(id).and_then(|meno| najdi_email(&meno))
}

G-5

Some(true)          // map: 5 > 3 = true, zabalí do Some
Some(5)             // filter: 5 > 3 je true, nechá Some(5)
None                // filter: 5 > 10 je false, vráti None
Some("5")           // map: 5.to_string() = "5"
Some(10)            // and_then: 5 > 3, tak Some(5 * 2) = Some(10)

H-1

use serde::{Serialize, Deserialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Clone)]
struct Produkt {
    nazov: String,
    cena: f64,
    kategoria: String,
    na_sklade: bool,
}

#[derive(Serialize, Deserialize, Default)]
struct Obchod {
    produkty: Vec<Produkt>,
}

impl Obchod {
    fn nacitaj_zo_suboru(cesta: &std::path::PathBuf) -> Option<Obchod> {
        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()
    }

    fn pridaj(&mut self, produkt: Produkt) -> Result<(), ()> {
        if self.produkty.iter().any(|p| p.nazov == produkt.nazov) {
            return Err(());
        }
        self.produkty.push(produkt);
        Ok(())
    }

    fn odstran(&mut self, nazov: &str) -> Result<Produkt, ()> {
        let pos = self.produkty.iter().position(|p| p.nazov == nazov).ok_or(())?;
        Ok(self.produkty.remove(pos))
    }

    fn najdi(&self, nazov: &str) -> Option<&Produkt> {
        self.produkty.iter().find(|p| p.nazov == nazov)
    }

    fn na_sklade(&self) -> Vec<&Produkt> {
        self.produkty.iter().filter(|p| p.na_sklade).collect()
    }

    fn v_kategorii(&self, kat: &str) -> Vec<&Produkt> {
        self.produkty.iter().filter(|p| p.kategoria == kat).collect()
    }

    fn najdrahsi(&self) -> Option<&Produkt> {
        self.produkty.iter().max_by(|a, b| a.cena.partial_cmp(&b.cena).unwrap())
    }

    fn priemerna_cena_v_kategorii(&self, kat: &str) -> Option<f64> {
        let v_kat: Vec<&Produkt> = self.v_kategorii(kat);
        if v_kat.is_empty() { return None; }
        let sucet: f64 = v_kat.iter().map(|p| p.cena).sum();
        Some(sucet / v_kat.len() as f64)
    }

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

H-2

use std::fmt;

enum Velkost { Mala, Stredna, Velka }

impl fmt::Display for Velkost {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Velkost::Mala => write!(f, "S"),
            Velkost::Stredna => write!(f, "M"),
            Velkost::Velka => write!(f, "L"),
        }
    }
}

struct Tricko { farba: String, velkost: Velkost, cena: f64 }

impl fmt::Display for Tricko {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Tričko [{}] {} - {:.2}€", self.velkost, self.farba, self.cena)
    }
}