Gépekkel suttogó

A berozsdásodott agytekervények átmozgatása egy kis Rust-tal

Beszélgessünk egy kicsit a nyelvtanulásról. No nem azoknak a fura nyelveknek a tanulásáról, amit a humanoidok egymás között használnak, hanem inkább a programozási nyelvek tanulásáról.

Egy ideje nézegetem már a Rust-ot, úgyhogy amikor olvastam egy játék fejlesztői blogján, hogy Rust-ban írták, ismét elkezdtem egy kicsit jobban utánanézni. Olvasgattam milyen játék motorok vannak Rust-hoz, ki is próbáltam néhány példakódot, de tisztában vagyok a (fejlesztői, de még inkább grafikusi) korlátaimmal, nem kezdenék neki egy játék fejlesztésének, főleg nem egy olyan nyelven, amit még nem is ismerek rendesen.

Úgyhogy inkább ismerősebb vizekre eveztem és megnéztem, hogy milyen webes keretrendszerek vannak, egész sokáig el is jutottam a leírásában az egyik szimpatikusabb darabnak, amikor kezdett már problémát jelenteni a példakódok maradéktalan értelmezése. Lehet, hogy nem ártana egy kis alapozás.

Manapság az Internet már teli van mindenféle online kurzusokkal, videóanyagokkal, de én a tradicionálisabb megoldások híve vagyok, úgyhogy a hivatalos Rust könyvnek estem neki. Nagyjából a feléig jutottam, amikor már nagyon kezdtem unni, hogy csak olvasok és nem pedig programozok.

Találtam is egy "munkafüzet" jellegű feladatgyűjteményt, ahol a kód nagy része meg van már írva, csak el kell érni, hogy leforduljon, vagy éppen a tesztek zöldek legyenek. Az alap gondolat szerintem nagyon jó, más nyelvekhez is találkoztam már ilyesmivel.

Az egyetlen probléma, hogy az ilyen jellegű feladatoknál hajlamos vagyok átkapcsolni tanulási üzemmódból feladat megoldási üzemmódba, ami arra nagyon jó, hogy gyorsan végigvittem ezt a "játékot", de a mélyebb megértést nem igazán segítette.

Néhány helyen azért sikerült lelassítani, ahol vagy sokadik próbálkozásra sem sikerült megoldani a feladatot, vagy nekiállni se tudtam, úgyhogy azért utána is olvastam néhány résznek. Az első megoldások sokszor nem is sikerültek túl szépre, nyilván nem ismerem a nyelv által nyújtott eszközöket, azzal dolgozok, amim van (vagy amit épp pár feladattal korábban tanultam). Például a hashmaps/hashmaps3.rs pálya első megoldása valami ilyesmi lett:

match scores.get_mut(&team_1_name) {
  Option::Some(team) => {
    team.goals_scored += team_1_score;
    team.goals_conceded += team_2_score;
  }
  Option::None => {
    scores.insert(
      team_1_name.clone(),
      Team { goals_scored: team_1_score, goals_conceded: team_2_score }
    );
  }
}
match scores.get_mut(&team_2_name) {
  Option::Some(team) => {
    team.goals_scored += team_2_score;
    team.goals_conceded += team_1_score;
  }
  Option::None => {
    scores.insert(
      team_2_name.clone(),
      Team { goals_scored: team_2_score, goals_conceded: team_1_score }
    );
  }
}

Aztán ráleltem a HashMap entry függvényére és az Entry or_insert függvényére, amik nagyban leegyszerűsítették a fenti kódot:

let team = scores.entry(team_1_name)
                 .or_insert(Team { goals_scored: 0, goals_conceded: 0 });
team.goals_scored += team_1_score;
team.goals_conceded += team_2_score;

let team = scores.entry(team_2_name)
                 .or_insert(Team { goals_scored: 0, goals_conceded: 0 });
team.goals_scored += team_2_score;
team.goals_conceded += team_1_score;

A Team később kapott még néhány extra függvényt...

impl Team {
  fn new() -> Self { Team { goals_scored: 0, goals_conceded: 0 } }
  fn add(&mut self, scored: u8, conceded: u8) {
    self.goals_scored += scored;
    self.goals_conceded += conceded;
  }
}

...és ez lett a végső megoldásom:

scores.entry(team_1_name)
      .or_insert(Team::new())
      .add(team_1_score, team_2_score);
scores.entry(team_2_name)
      .or_insert(Team::new())
      .add(team_2_score, team_1_score);

Miután a Rustlings pályáival végeztem, új feladat után kellett néznem. Egy webes projektet még mindig túl nagy falatnak éreztem. Régen, amikor Python-t tanultam, akkor Project Euler feladatokat oldottam meg vele gyakorlásként. Hasonló ötlettel mentem tovább most is.

A 2023-as Advent of Code-ba (is) belekezdtem tavaly decemberben és bár nem jutottam túl sokáig, az első néhány napra lett megoldásom PHP-ban (fő a változatosság), amiket újragondolhatok Rust-ban.

A meglévő megoldások ugyan nem előfeltételei annak, hogy az Advent of Code feladatai jól használhatóak legyenek egy kis programozási gyakorlásra, de én személy szerint nem szeretem, ha két zavaró tényező is van: az egyik az, hogy nem tudom még, hogy hogyan kell megoldani a feladatot, a másik pedig az, hogy nem értek a Rust-hoz. Az általában csak frusztrációhoz vezet és elmegy az ember kedve az egésztől.

Nyilván az Interneten fellelhető egy csomó Advent of Code megoldás, de akkor meg azzal megy az idő, hogy mások kódját megértse az ember, szóval jobb az, ha saját megoldással megyünk.

Alább az első nap első felének a megoldása, szerintem egész Rust-szerű lett:

use std::fs;

fn main() {
  let sum: u32 = fs::read_to_string("data/input_1.txt").unwrap()
    .lines()
    .map(calibration_value)
    .sum();

  println!("{:?}", sum);
}

fn calibration_value(line: &str) -> u32 {
  let numbers: Vec<u32> = line.chars()
    .filter_map(|c| c.to_digit(10))
    .collect();

  numbers.first().unwrap() * 10 + numbers.last().unwrap()
}

Érdemes összevetni a PHP-s implementációmmal, ami egy kicsit... hogy is mondjam... kevésbé sikerült elegánsra (bár akkoriban a cél a feladat megoldása volt, nem pedig az, hogy gyönyörű PHP kód szülessen):

<?php

$sum = 0;
foreach (file('input_1.txt') as $line) {
  $data = trim($line);
  preg_match('/^.*?(?P<digit>[0-9])/', $data, $m);
  $firstDigit = $m['digit'];
  preg_match('/.*(?P<digit>[0-9]).*?$/', $data, $m);
  $lastDigit = $m['digit'];

  $sum += intval($firstDigit . $lastDigit);
}

print("$sum\n");

Egy másik jó irány lehet egy új nyelv (és a hozzá tartozó teszt környezet) gyakorlására a kód katák. Főleg azok, amiket más nyelveken már megoldottunk, hogy a feladat megoldása már ne okozzon semmilyen súrlódást. De a kata feladatok általában egyébként sem túl bonyolultak, úgyhogy akár előzetes felkészülés nélkül is bele lehet vágni.

Szóval így teltek a napjaim a Rust társaságában. A következő lépés valószínűleg az lesz, hogy visszatérek egy kicsit a könyvhöz, hogy a Rust sajátosságait (mint például az ownership/borrowing) jobban megértsem és mellette csinálom tovább a kis feladatokat. Te hogyan állsz neki egy új programozási nyelv tanulásának?

This post is also available in english: Machine whisperer

Hozzáfűznél valamit?

Dobj egy emailt a blog kukac deadlime pont hu címre.

Feliratkoznál?

Az RSS feed-et ajánljuk, ha kedveled a régi jó dolgokat.