Seit wenigen Tagen beschäftige ich mich mit Rust und habe als Übung ein Programm erstellt, mit dem ich meine Tankstatistik auswerten kann. Die Daten habe ich mit meiner Android-App Tankstatistik gesammelt und exportiert.
Die Daten müssen in einer Datei „tanken.txt“ im Hauptverzeichnis des Rust-Programmes gespeichert werden und sehen wie folgt aus:
1 2 3 4 5 6 |
19.11.2022 112443 29 49,27 04.12.2022 112872 30,01 49,79 19.12.2022 113361 33 52,11 16.01.2023 113824 33,6 55,41 01.02.2023 114335 34 58,11 15.02.2023 114856 33 56,07 |
In der ersten Spalte steht das Tankdatum, in der zweiten der Kilometerstand, in der dritten die getankten Liter und in der vierten der Preis. Die beiden letzten Zahlen benutzen als Dezimalpunkt das Komma, damit man die Daten leichter in das deutschsprachige Excel importieren kann. Die Spalten sind mit dem Tabulatorzeichen (\t) getrennt. Die Daten können hier heruntergeladen werden.
Das Rust-Programm besteht aus zwei Dateien „main.rs“ und „lib.rs“. Dabei benutzt das Hauptprogramm „main.rs“ die Funktionen aus der Bibliothek „lib.rs“.
Das Programm kann bei GitHub ausgecheckt werden.
Das Projekt verwendet einige Features von Rust wie Structs mit Datenfeldern und Implementierungen von Funktionen und Methoden, Collections wie Vec und HashMap, Closures, Iteratoren, Dateizugriffe, Sortieren und Tests.
Die Datei „lib.rs“ sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
use std::collections::HashMap; use std::fs::File; use std::io::BufRead; use std::io::BufReader; #[derive(Debug)] pub struct Date { pub day: u8, pub month: u8, pub year: u16, } impl Date { pub fn new(datetext: &str) -> Self { let mut split = datetext.split("."); Date { day: split.next().unwrap().parse().unwrap(), month: split.next().unwrap().parse().unwrap(), year: split.next().unwrap().parse().unwrap(), } } } #[derive(Debug)] pub struct Record { pub date: Date, pub km: u32, pub liter: f32, pub costs: f32, } impl Record { pub fn new(line: &str) -> Record { let mut split = line.split("\t"); Record { date: Date::new(split.next().unwrap()), km: split.next().unwrap().parse().unwrap(), liter: Self::parse_german(split.next().unwrap()), costs: Self::parse_german(split.next().unwrap()), } } fn parse_german(numtext: &str) -> f32 { numtext.replace(",", ".").parse().unwrap() } } #[derive(Debug)] pub struct Statistics { data: Vec<Record>, } impl Statistics { pub fn new(filename: &str) -> Statistics { Statistics { data: Self::parse_file(filename), } } fn parse_file(filename: &str) -> Vec<Record> { let file = File::open(filename).unwrap(); let mut stats: Vec<Record> = Vec::new(); BufReader::new(file).lines().for_each(|line| { stats.push(Record::new(&line.unwrap())); }); stats } pub fn get_years<F>(&self, f: F) -> Vec<(u16, f32)> where F: Fn(&Record) -> f32, { let mut map: HashMap<u16, f32> = HashMap::new(); self.data.iter().for_each(|rec| { map.entry(rec.date.year) .and_modify(|v| *v = *v + f(rec)) .or_insert(f(rec)); }); let mut years: Vec<(u16, f32)> = map.into_iter().collect(); years.sort_by_key(|v| v.0); years } pub fn get_kilometers(&self) -> u32 { self.data.last().unwrap().km - self.data.first().unwrap().km } pub fn get_consumption(&self) -> f32 { let mut liter: f32 = self.data.iter().map(|r| r.liter).sum(); liter -= self.data.last().unwrap().liter; liter / self.get_kilometers() as f32 * 100 as f32 } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_date() { let d = Date::new("7.04.2022"); assert_eq!(d.day, 7); assert_eq!(d.month, 4); assert_eq!(d.year, 2022); } #[test] fn parse_record() { let r = Record::new("7.04.2022\t100000\t90,5\t120,6"); assert_eq!(r.date.day, 7); assert_eq!(r.date.month, 4); assert_eq!(r.date.year, 2022); assert_eq!(r.km, 100000); assert_eq!(r.liter, 90.5); assert_eq!(r.costs, 120.6); } } |
Die Datei „main.rs“ sieht so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
use tanken::Statistics; fn main() { let stats = Statistics::new("./tanken.txt"); println!("Kosten pro Jahr:"); println!("----------------"); stats .get_years(|r| r.costs) .iter() .for_each(|(y, c)| println!("Jahr {}: {:>8.2} Euro.", y, c)); println!(); println!("Liter pro Jahr:"); println!("---------------"); stats .get_years(|r| r.liter) .iter() .for_each(|(y, c)| println!("Jahr {}: {:>8.2} Liter.", y, c)); println!(); println!("Gefahrene Kilometer: {}.", stats.get_kilometers()); println!(); println!("Verbrauch {:.2} Liter pro 100 km.", stats.get_consumption()); } |
Wenn Du das mal ausprobieren willst, dann erstelle in einem freien Verzeichnis das Projekt „tanken“ mit:
>cargo new tanken
>cd tanken
Kopiere dann die beiden Dateien „main.rs“ und „lib.rs“ in das „src“-Unterverzeichnis und die Datei „tanken.txt“ in das Hauptverzeichnis „tanken“. Jetzt kannst Du die Tests laufen lassen mit:
>cargo test
Und das Programm kannst Du laufen lassen mit:
>cargo run
Dann solltest Du folgendes sehen:
Viel Spaß!