Cél és előfeltétel

Egy jól olvasható riport nem változtatja meg véletlenül a sorrendjét két azonos feldolgozás között. Ebben a leckében BTreeMapből készítesz rendezett szöveget, majd a forrássorok megtartását és a duplikált kulcsok kezelését kapcsolod hozzá. Előfeltétel az iterátoros validáció, valamint a típusos hibák. Minden kód önálló, edition 2024 program beágyazott próbaadatokkal.

A stabil sorrendhez meg kell mondanod, mi szerint rendezel. A BTreeMap szöveges kulcsai Rust szerinti összehasonlítást használnak, nem magyar nyelvi szótári szabályt. Ezt a választást a riport szerződésének részeként kezeljük. A determinisztikus megjelenítés nem jelenti azt, hogy minden eltérő bemeneti fájl ugyanazt a riportot adja: az eredeti sorszámok például a dokumentum elrendezésével változhatnak.

Értéktábla és diagnosztikai eredet

Egy beállítás kulcsa és értéke a logikai tartalomhoz tartozik. A sorszám azt mondja meg, hol találtuk a forrásban. A kulcs szerinti rendezés nem írhatja át az eredeti sorszámot, mert az a hibakereséshez szükséges eredetadat. Ezért külön mezőben tároljuk, és nem az új megjelenítési pozícióból következtetünk rá.

A beépített insert művelet már létező kulcsnál lecserélheti az értéket. Egy konfigurációs nyelv választhatna „utolsó nyer” szabályt, de a mi végső parserünk ezt nem engedi: a duplikáció hiba. A rendezett adatstruktúra önmagában nem ellenőrzi ezt az üzleti szabályt. A felvételi logikában kell eldöntenünk, hogy új bejegyzésről vagy ütközésről van szó.

1. feladat: azonos tartalom, eltérő beszúrási sorrend

Írj kulcs=érték sorokat készítő függvényt. Két térkép ugyanazokat az adatokat fordított beszúrási sorrendben kapja. A riport bájtról bájtra legyen azonos, a sorokat mindig newline zárja. A példa kulcsai és értékei nem tartalmaznak sorvéget; ilyen adatot a soronkénti parser sem hozna létre egyetlen mezőként.

Megoldás
use std::collections::BTreeMap;
use std::fmt::Write;
fn render(values: &BTreeMap<&str, &str>) -> String {
    let mut report = String::new();
    for (key, value) in values {
        writeln!(&mut report, "{key}={value}").unwrap();
    }
    report
}
fn main() {
    let first = BTreeMap::from([("z", "2"), ("a", "x=y")]);
    let second = BTreeMap::from([("a", "x=y"), ("z", "2")]);
    assert_eq!(render(&first), "a=x=y\nz=2\n");
    assert_eq!(render(&first), render(&second));
    assert_eq!(first.get("a"), Some(&"x=y"));
    assert_eq!(render(&BTreeMap::new()), "");
}

A jelentés nem változtatja meg a forrást. Az értékben lévő egyenlőségjel sem gond, mert a korábbi szerződés szerint a visszaolvasás csak az első jelnél bontana. A riport nem őrzi meg a kommenteket, szóközöket vagy a beszúrás eredeti sorrendjét. Ez normalizált megjelenítés, nem a bemeneti dokumentum bájtpontos rekonstrukciója.

2. feladat: rendezz a forrásszám átírása nélkül

A bejegyzéshez érték és eredeti sor is tartozzon. Készíts diagnosztikai riportot, amely kulcs szerint rendez, de az eredeti helyet írja ki. Ellenőrizd, hogy az elsőként megjelenő kulcs sorszáma nem feltétlenül egy. A diagnosztikai formátum most nem a konfiguráció visszaolvasási formátuma.

Megoldás
use std::collections::BTreeMap;
use std::fmt::Write;
struct Item<'a> { value: &'a str, line: usize }
fn main() {
    let items = BTreeMap::from([
        ("z", Item { value: "2", line: 1 }),
        ("a", Item { value: "1", line: 4 }),
    ]);
    let mut report = String::new();
    for (key, item) in &items {
        writeln!(&mut report, "{key}={} (eredeti sor: {})",
            item.value, item.line).unwrap();
    }
    assert_eq!(report, "a=1 (eredeti sor: 4)\nz=2 (eredeti sor: 1)\n");
    assert_eq!(items.get("a").unwrap().line, 4);
}

A kimeneti sorrend és az eredet két külön koordináta. Ha a felhasználó egy üres sort beszúr a dokumentum elejére, a logikai beállítások maradhatnak azonosak, a diagnosztikai sorhelyek viszont változnak. Ezt nem érdemes „nem determinisztikusnak” nevezni: az eredmény a megváltozott bemeneti eredetet tükrözi. A tesztben mindig mondd meg, mely tulajdonságok egyezését várod.

3. feladat: duplikáció ne írja felül az első értéket

Írj egyetlen bejegyzést felvevő függvényt Entry segítségével. Már létező kulcsnál Err legyen az eredmény, a korábbi érték pedig maradjon meg. Ugyanaz a kulcs ugyanazzal az értékkel is duplikáció ebben a nyelvben. A helyi bejegyzési szabályt majd a végső parser kapcsolja az eredeti sorszámhoz.

Megoldás
use std::collections::{BTreeMap, btree_map::Entry};
fn add<'a>(values: &mut BTreeMap<&'a str, &'a str>, key: &'a str, value: &'a str)
    -> Result<(), &'static str>
{
    match values.entry(key) {
        Entry::Vacant(slot) => { slot.insert(value); Ok(()) }
        Entry::Occupied(_) => Err("duplikalt kulcs"),
    }
}
fn main() {
    let mut values = BTreeMap::new();
    add(&mut values, "port", "8080").unwrap();
    assert_eq!(add(&mut values, "port", "9090"), Err("duplikalt kulcs"));
    assert_eq!(add(&mut values, "port", "8080"), Err("duplikalt kulcs"));
    assert_eq!(values.get("port"), Some(&"8080"));
    assert_eq!(values.len(), 1);
}

A foglalt ág egyáltalán nem helyettesíti a korábbi értéket. Az elutasítás és a megőrzött tartalom együttes állítása erősebb, mint pusztán az Err ellenőrzése. A példában a kulcs pontos szövegegyezés szerint számít azonosnak. Kisbetűsítést vagy Unicode-normalizálást nem vezetünk be csendben, mert azok megváltoztatnák az azonos kulcs jelentését.

A riport formátuma is változtatható szerződés

Ha a kimenetet később más program olvassa, a sorvég, az elválasztó és az üres dokumentum alakja ugyanúgy számít, mint a szép megjelenítés. Ne adj hozzá magyarázó fejlécet egy már feldolgozott adatkimenethez külön döntés nélkül. Egy embernek szánt diagnosztikai szöveg és egy visszaolvasható konfiguráció eltérő formátum lehet, ezért a két függvényt is érdemes külön tartani.

A stabil riport segít a változások áttekintésében, de nem biztonsági hitelesítés és nem titokvédelmi mechanizmus. Jelszó vagy token értékét nem indokolt automatikusan diagnosztikába írni attól, hogy szépen rendezhető. A mai beágyazott példák nem tartalmaznak titkot; egy valódi felületben külön kellene meghatározni a látható és maszkolandó mezőket.

A rendezett megjelenítéshez az összes szükséges kulcsot tároljuk. Ez más költségmodell, mint a beérkező sorok azonnali továbbírása. A helyes sorrendért vállalt tárolási döntést ne nevezd önmagában hibának vagy optimalizálásnak: a felhasználói szerződésből következik. Nagy bemenet esetén a méretkorlátot és a feldolgozás módját ehhez kell igazítani.

Elfogadási feltételek

  1. Azonos kulcs-érték tartalom beszúrási sorrendtől függetlenül azonos, rendezett riportot ad.
  2. A kulcs szerinti rendezés nem számozza át az eredeti forrássorokat.
  3. Duplikációkor az első érték változatlan marad, ugyanaz az érték ismételten sem írható be.

Összefoglaló és továbblépés

A konfiguráció-parser miniprojektben a sorvalidáció és a duplikációellenőrzés egyetlen, típusos hibát adó felületté áll össze. A végső eredmény csak teljes siker esetén lesz alkalmazható konfiguráció.