summaryrefslogtreecommitdiff
path: root/einreichung/Aufgabe3/Quelltext
diff options
context:
space:
mode:
authorMalte Voos <git@mal.tc>2022-08-20 14:12:11 +0200
committerMalte Voos <git@mal.tc>2022-08-20 14:12:11 +0200
commitd0c70ad88b11f412c9d8c6735b74cce1b1ff015b (patch)
tree2f0e9821a785e083abfe851ce919cadbefe001af /einreichung/Aufgabe3/Quelltext
parent5f22745507a343163521fbe85cdc72ac144c319f (diff)
downloadbwinf402-main.tar.gz
bwinf402-main.zip
cleanupHEADmain
Diffstat (limited to 'einreichung/Aufgabe3/Quelltext')
-rw-r--r--einreichung/Aufgabe3/Quelltext/Cargo.lock7
-rw-r--r--einreichung/Aufgabe3/Quelltext/Cargo.toml8
-rw-r--r--einreichung/Aufgabe3/Quelltext/src/main.rs655
3 files changed, 670 insertions, 0 deletions
diff --git a/einreichung/Aufgabe3/Quelltext/Cargo.lock b/einreichung/Aufgabe3/Quelltext/Cargo.lock
new file mode 100644
index 0000000..f2778c5
--- /dev/null
+++ b/einreichung/Aufgabe3/Quelltext/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aufgabe3"
+version = "0.1.0"
diff --git a/einreichung/Aufgabe3/Quelltext/Cargo.toml b/einreichung/Aufgabe3/Quelltext/Cargo.toml
new file mode 100644
index 0000000..94f1a9c
--- /dev/null
+++ b/einreichung/Aufgabe3/Quelltext/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "aufgabe3"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/einreichung/Aufgabe3/Quelltext/src/main.rs b/einreichung/Aufgabe3/Quelltext/src/main.rs
new file mode 100644
index 0000000..fbf554d
--- /dev/null
+++ b/einreichung/Aufgabe3/Quelltext/src/main.rs
@@ -0,0 +1,655 @@
+use std::collections::VecDeque;
+use std::env;
+use std::fmt::Display;
+use std::fmt::Write;
+use std::fs;
+use std::process;
+use std::str::FromStr;
+use std::time;
+
+// Beschreibt eine einzelne Hex-Ziffer. Der enthaltene Integer nimmt nur die
+// Werte 0x0 bis 0xF an.
+#[derive(Clone, Copy)]
+struct HexDigit(u8);
+
+// Beschreibt eine Hex-Zahl mit beliebig vielen Ziffern. Die Ziffern sind in
+// Lesereihenfolge gespeichert.
+struct HexNumber {
+ digits: Vec<HexDigit>,
+}
+
+// Beschreibt eine zu lösende Aufgabe.
+struct Task {
+ number: HexNumber,
+ max_moves: usize,
+}
+
+// Beschreibt den Zustand einer Siebensegmentanzeige als 8-Bit-Zahl.
+//
+// Die ersten sieben Bits (von rechts) geben jeweils an, ob das entsprechende
+// Segment der Siebensegmentanzeige "an" ist bzw. ein Stäbchen enthält: wenn
+// ja, ist das Bit eine 1; anderenfalls eine 0. Das letzte Bit ist immer eine
+// 0.
+//
+// Die Reihenfolge der Segmente entspricht dieser Abbildung:
+// https://en.wikipedia.org/wiki/Seven-segment_display#/media/File:7_Segment_Display_with_Labeled_Segments.svg
+//
+// Allgemein wird der Zustand der Siebensegmentanzeige also durch die Zahl
+// 0b0GFEDCBA repräsentiert.
+#[derive(Clone, Copy)]
+struct SevenSegmentDisplay(u8);
+
+// Beschreibt den Zustand einer Reihe von Siebensegmentanzeigen.
+struct SevenSegmentDisplayRow {
+ displays: Vec<SevenSegmentDisplay>,
+}
+
+// Vom Algorithmus zurückgegebene Lösung.
+// Der Typ enthält ein paar redundante Informationen, die aber die Ausgabe des
+// Programms anschaulicher machen.
+struct Solution {
+ // Die gegebene Anfangs-Hex-Zahl.
+ initial: HexNumber,
+ // Alle Zwischenstände, einschließlich des Anfangs- und Endzustandes.
+ states: Vec<SevenSegmentDisplayRow>,
+ // Die ermittelte größtmögliche Hex-Zahl.
+ r#final: HexNumber,
+ // Die gegebene Maximalzahl an Umlegungen.
+ max_moves: usize,
+ // Die Anzahl der tatsächlich genutzten Umlegungen.
+ used_moves: usize,
+}
+
+// Löst eine gegebene Aufgabe, indem Teil 1 und 2 des Algorithmus nacheinander
+// aufgerufen werden.
+fn solve_task(task: Task) -> Solution {
+ let greatest_reachable_number = solve_pt1(&task);
+ let states = solve_pt2(&task.number.digits, &greatest_reachable_number);
+
+ Solution {
+ initial: task.number,
+ r#final: HexNumber {
+ digits: greatest_reachable_number,
+ },
+ max_moves: task.max_moves,
+ used_moves: states.len() - 1,
+ states,
+ }
+}
+
+// Implementiert den ersten Teil des Algorithmus, der aus einer Aufgabe die
+// größtmögliche Hex-Zahl errechnet.
+fn solve_pt1(task: &Task) -> Vec<HexDigit> {
+ // `solve_pt1_internal` ist der rekursive Teil der Funktion, der versucht,
+ // den gegebenen Ausschnitt der Hex-Zahl zu maximieren.
+ fn solve_pt1_internal(
+ // Der zu maximierende Ausschnitt der Hex-Zahl.
+ digits: &[HexDigit],
+ // `segment_balance` enthält im Laufe der Rekursion immer den
+ // "Kontostand" der Stäbchen. Wenn es einen Überschuss bzw. Mangel an
+ // Stäbchen gibt, ist `segment_balance` positiv bzw. negativ. Nur wenn
+ // `segment_balance` am Ende 0 ist, kann die neue Ziffernreihe durch
+ // Umlegungen realisiert werden.
+ segment_balance: isize,
+ // `segment_flips_left` gibt an, wie viele Segmente noch "geflippt"
+ // (d.h. gefüllt oder geleert) werden können, ohne die Maximalzahl an
+ // Umlegungen zu überschreiten.
+ segment_flips_left: usize,
+ // `vacant_segments_left` gibt an, wie viele überschüssige Stäbchen
+ // theoretisch noch in den hinteren Ziffern untergebracht werden
+ // könnten, wenn diese zu '8' (Ziffer bestehend aus den meisten
+ // Stäbchen) aufgefüllt würden.
+ vacant_segments_left: usize,
+ // `occupied_segments_left` gibt an, wie viele fehlende Stäbchen
+ // theoretisch noch aus den hinteren Ziffern entnommen werden könnten,
+ // wenn diese zu '1' (Ziffer bestehend aus den wenigsten Stäbchen)
+ // reduziert werden.
+ occupied_segments_left: usize,
+ ) -> Option<Vec<HexDigit>> {
+ // Es gibt mehrere Backtracking-Bedingungen, die signalisieren, dass
+ // dieser Pfad zu keiner gültigen Lösung führen kann.
+ if
+ // 1. Der Überschuss bzw. Mangel an Stäbchen ist so groß, dass er nicht
+ // mehr ausgeglichen werden kann, ohne die Maximalzahl an Umlegungen zu
+ // überschreiten.
+ segment_balance.abs() as usize > segment_flips_left
+ // 2. Der Überschuss an Stäbchen ist so groß, dass er nicht mehr
+ // ausgeglichen kann, selbst wenn alle übrigen Ziffern zu '8'
+ // aufgefüllt werden.
+ || segment_balance > vacant_segments_left as isize
+ // 3. Der Mangel an Stäbchen ist so groß, dass er nicht mehr
+ // ausgeglichen werden kann, selbst wenn alle übrigen Ziffern zu
+ // '1' reduziert werden.
+ || -segment_balance > occupied_segments_left as isize
+ {
+ return None;
+ }
+ // Es wird immer nur die erste Ziffer betrachtet, und die restlichen
+ // Ziffern werden rekursiv ergänzt.
+ match digits.split_first() {
+ // Falls schon alle Ziffern betrachtet wurden, handelt es sich nur
+ // um eine gültige Lösung, wenn der Stäbchen-Kontostand
+ // `segment_balance` gleich 0 ist (s.o.).
+ None => match segment_balance {
+ 0 => Some(Vec::new()),
+ _ => None,
+ },
+ Some((digit, rest)) => {
+ // Die Anzahl der Stäbchen, aus der die erste Ziffer besteht.
+ let digit_num_sticks = digit.num_sticks();
+ // `vacant_segments_left` und `occupied_segments_left` müssen
+ // bezogen auf die restlichen Stäbchen angepasst werden.
+ // '8', die Ziffer bestehend aus den meisten Stäbchen, enthält
+ // 7 Stäbchen.
+ let new_vacant_segments_left = vacant_segments_left - (7 - digit_num_sticks);
+ // '1', die Ziffer bestehend aus den wenigsten Stäbchen,
+ // enthält 2 Stäbchen.
+ let new_occupied_segments_left = occupied_segments_left - (digit_num_sticks - 2);
+
+ // Es wird versucht, die erste Ziffer zu maximieren. Dafür
+ // werden alle möglichen Hex-Ziffern absteigend durchgegangen
+ // und jeweils versucht, die erste Ziffer auf die gewählte
+ // Ziffer zu setzen und davon ausgehend die restlichen Ziffern
+ // anzupassen, sodass am Ende die gefundene maximale Hex-Zahl
+ // durch eine nicht zu hohe Anzahl an Umlegungen erreicht
+ // werden kann.
+ for candidate_digit in (0x0..=0xF).rev().map(HexDigit) {
+ // `segment_num_difference` ist die Differenz zwischen der
+ // alten und der neuen Anzahl an Stäbchen für die erste
+ // Ziffer.
+ let segment_num_difference =
+ digit_num_sticks as isize - candidate_digit.num_sticks() as isize;
+ // `num_required_segment_flips` ist die erforderliche
+ // Anzahl an "Stäbchen-Flips" (s.o.), um von der alten
+ // ersten Ziffer zur neuen zu kommen.
+ let num_required_segment_flips =
+ digit.num_required_segment_flips(candidate_digit);
+
+ // Der "Stäbchen-Kontostand" sowie die noch verfügbare
+ // Anzahl an "Stäbchen-Flips" werden bezogen auf die
+ // restlichen Ziffern angepasst.
+ let new_segment_balance = segment_balance + segment_num_difference;
+ let new_segment_flips_left =
+ match segment_flips_left.checked_sub(num_required_segment_flips) {
+ // Wenn schon die Änderung der ersten Ziffer nicht
+ // mehr mit den übrigen "Stäbchen-Flips" zu
+ // bewerkstelligen ist, kann sofort zum
+ // nächstniedrigeren Wert für die erste Ziffer
+ // übergegangen werden.
+ None => continue,
+ Some(x) => x,
+ };
+
+ match (new_segment_flips_left, new_segment_balance) {
+ // Wenn keine weiteren Umlegungen mehr möglich sind und
+ // es keinen Mangel oder Überschuss an Stäbchen gibt,
+ // ist die Lösung gefunden. Die restlichen Ziffern
+ // bleiben unverändert.
+ (0, 0) => {
+ let mut ret = vec![candidate_digit];
+ ret.append(&mut rest.to_vec());
+ return Some(ret);
+ }
+ // Wenn keine weiteren Umlegungen mehr möglich sind,
+ // aber es einen Mangel oder Überschuss an Stäbchen
+ // gibt, wird der nächstniedrigere Wert für die erste
+ // Ziffer probiert.
+ (0, _) => continue,
+ // Ansonsten wird versucht, die restlichen Ziffern
+ // rekursiv zu maximieren.
+ (_, _) => {
+ let mut new_rest = match solve_pt1_internal(
+ rest,
+ new_segment_balance,
+ new_segment_flips_left,
+ new_vacant_segments_left,
+ new_occupied_segments_left,
+ ) {
+ // Falls dies nicht gelingt, wird der
+ // nächstniedrigere Wert für die erste Ziffer
+ // probiert.
+ None => continue,
+ Some(x) => x,
+ };
+ let mut ret = vec![candidate_digit];
+ ret.append(&mut new_rest);
+ return Some(ret);
+ }
+ }
+ }
+ // Der Pfad wird verworfen, da unter den gegebenen Bedingungen
+ // für keinen Wert der ersten Ziffer eine gültige neue
+ // Ziffernfolge gefunden werden konnte.
+ None
+ }
+ }
+ }
+
+ // Die Maximalzahl an "Stäbchen-Flips" ist zweimal die Maximalzahl an
+ // Umlegungen, da eine Umlegung aus zwei "Stäbchen-Flips" besteht: an einer
+ // Position wird ein Stäbchen entfernt und an einer zweiten ein Stäbchen
+ // abgelegt.
+ let max_segment_flips = 2 * task.max_moves;
+ // Die Startwerte für `vacant_segments_left` und `occupied_segments_left`
+ // werden ermittelt, indem einmal über alle Ziffern iteriert wird.
+ let (initial_vacant_segments, initial_occupied_segments) = task.number.digits.iter().fold(
+ (0, 0),
+ |(acc_vacant_segments, acc_occupied_segments), digit| {
+ let num_sticks = digit.num_sticks();
+ (
+ acc_vacant_segments + (7 - num_sticks),
+ acc_occupied_segments + (num_sticks - 2),
+ )
+ },
+ );
+
+ solve_pt1_internal(
+ &task.number.digits,
+ 0,
+ max_segment_flips,
+ initial_vacant_segments,
+ initial_occupied_segments,
+ )
+ .unwrap()
+}
+
+// Implementiert den zweiten Teil des Algorithmus, der aus der gegebenen
+// Hex-Zahl und der errechneten größtmöglichen Hex-Zahl eine Abfolge von
+// Umlegungen erzeugt.
+fn solve_pt2(start: &[HexDigit], end: &[HexDigit]) -> Vec<SevenSegmentDisplayRow> {
+ // Zunächst werden die beiden Hex-Zahlen jeweils zu einem Array von
+ // Siebensegmentanzeigen konvertiert.
+ let start_state = start
+ .into_iter()
+ .map(|digit| digit.to_seven_segments())
+ .collect::<Vec<SevenSegmentDisplay>>();
+ let end_state = end
+ .into_iter()
+ .map(|digit| digit.to_seven_segments())
+ .collect::<Vec<SevenSegmentDisplay>>();
+
+ // In diesem Array werden die Zwischenstände zwischen den Umlegungen
+ // gesammelt. Diese werden am Ende auch zurückgegeben.
+ let mut states = vec![SevenSegmentDisplayRow {
+ displays: start_state.clone(),
+ }];
+
+ // Beschreibt die Position eines Segments in einer Reihe von
+ // Siebensegmentanzeigen.
+ struct Coordinates {
+ display_idx: usize,
+ segment_idx: u8,
+ }
+
+ let mut current_state = start_state.clone();
+
+ // In diesen beiden Arrays werden die Positionen der Segmente gespeichert,
+ // die "ein-" bzw. "ausgeschaltet" werden müssen, um von der urspünglichen
+ // Hex-Zahl zur größtmöglichen zu kommen, und die nicht schon durch eine
+ // Umlegung innerhalb einer Siebensegmentanzeige richtiggestellt werden
+ // konnten.
+ let mut unmatched_on_flips: Vec<Coordinates> = Vec::new();
+ let mut unmatched_off_flips: Vec<Coordinates> = Vec::new();
+
+ for (display_idx, (segments_start, segments_end)) in
+ start_state.iter().zip(end_state.iter()).enumerate()
+ {
+ // In diesen beiden Arrays werden die Indizes der Segmente gespeichert,
+ // die in dieser Siebensegmentanzeige "ein-" bzw. "ausgeschaltet"
+ // werden müssen.
+ let mut on_flips = VecDeque::<u8>::new();
+ let mut off_flips = VecDeque::<u8>::new();
+
+ for (segment_idx, (segment_start, segment_end)) in segments_start
+ .into_iter()
+ .zip(segments_end.into_iter())
+ .enumerate()
+ {
+ match (segment_start, segment_end) {
+ (false, true) => on_flips.push_back(segment_idx as u8),
+ (true, false) => off_flips.push_back(segment_idx as u8),
+ _ => {}
+ }
+ }
+
+ // `num_intra_display_moves` ist die Anzahl der Umlegungen, die
+ // innerhalb dieser Siebensegmentanzeige erfolgen können.
+ let num_intra_display_moves = on_flips.len().min(off_flips.len());
+ // Es werden vorab schon innerhalb dieser Siebensegmentanzeige
+ // `num_intra_display_moves` Umlegungen ausgeführt, indem gleichzeitig
+ // über die ersten `num_intra_display_moves` "On-Flips" und "Off-Flips"
+ // iteriert wird. Nach jeder Umlegung wird eine Kopie des
+ // Gesamt-Zwischenstandes gespeichert.
+ for (on_flip, off_flip) in on_flips
+ .drain(..num_intra_display_moves)
+ .zip(off_flips.drain(..num_intra_display_moves))
+ {
+ current_state[display_idx].toggle(on_flip);
+ current_state[display_idx].toggle(off_flip);
+ states.push(SevenSegmentDisplayRow {
+ displays: current_state.clone(),
+ })
+ }
+
+ // Übrig gebliebene "Stäbchen-Flips", die nicht durch eine Umlegung
+ // innerhalb dieser Siebensegmentanzeige realisiert werden konnten,
+ // werden für die spätere Verarbeitung gespeichert.
+ let complete_coords = |segment_idx| Coordinates {
+ display_idx,
+ segment_idx,
+ };
+ unmatched_on_flips.extend(on_flips.drain(..).map(complete_coords));
+ unmatched_off_flips.extend(off_flips.drain(..).map(complete_coords));
+ }
+
+ // Sanity Check
+ assert_eq!(unmatched_on_flips.len(), unmatched_off_flips.len());
+
+ // Zuletzt wird gleichzeiting über die noch nicht realisierten
+ // "Stäbchen-Flips" iteriert. Es wird jeweils die beschriebene Umlegung
+ // ausgeführt, und nach jeder Umlegung wird eine Kopie des Zwischenstandes
+ // gespeichert, sodass am Ende die genaue Umlegungs-Abfolge einsehbar ist.
+ for (on_flip, off_flip) in unmatched_on_flips
+ .into_iter()
+ .zip(unmatched_off_flips.into_iter())
+ {
+ current_state[on_flip.display_idx].toggle(on_flip.segment_idx);
+ current_state[off_flip.display_idx].toggle(off_flip.segment_idx);
+ states.push(SevenSegmentDisplayRow {
+ displays: current_state.clone(),
+ })
+ }
+
+ states
+}
+
+impl HexDigit {
+ // Stellt eine Hex-Ziffer auf einer Siebensegmentanzeige dar.
+ fn to_seven_segments(self) -> SevenSegmentDisplay {
+ let segments = match self.0 {
+ 0x0 => 0b0111111,
+ 0x1 => 0b0000110,
+ 0x2 => 0b1011011,
+ 0x3 => 0b1001111,
+ 0x4 => 0b1100110,
+ 0x5 => 0b1101101,
+ 0x6 => 0b1111101,
+ 0x7 => 0b0000111,
+ 0x8 => 0b1111111,
+ 0x9 => 0b1101111,
+ 0xA => 0b1110111,
+ 0xB => 0b1111100,
+ 0xC => 0b0111001,
+ 0xD => 0b1011110,
+ 0xE => 0b1111001,
+ 0xF => 0b1110001,
+ _ => unreachable!(),
+ };
+ SevenSegmentDisplay(segments)
+ }
+
+ // Gibt die Anzahl der Stäbchen zurück, die für die Darstellung dieser
+ // Hex-Ziffer auf einer Siebensegmentanzeige benötigt werden.
+ fn num_sticks(self) -> usize {
+ self.to_seven_segments().0.count_ones() as usize
+ }
+
+ // Gibt die Anzahl der "Stäbchen-Flips" zurück, die benötigt werden, um
+ // von der Siebensegmentdarstellung dieser Hex-Ziffer zu der einer anderen
+ // zu gelangen.
+ fn num_required_segment_flips(self, other: Self) -> usize {
+ (self.to_seven_segments().0 ^ other.to_seven_segments().0).count_ones() as usize
+ }
+}
+
+impl SevenSegmentDisplay {
+ // Gibt das `idx`-te Segment dieser Siebensegmentanzeige zurück.
+ fn get(self, idx: u8) -> bool {
+ self.0 & (1 << idx) != 0
+ }
+
+ // Schaltet das `idx`-te Segmente dieser Siebensegmentanzeige um.
+ fn toggle(&mut self, idx: u8) {
+ self.0 ^= 1 << idx;
+ }
+}
+
+// Iterator über die Segmente einer Siebensegmentanzeige.
+struct SevenSegmentDisplayIterator {
+ idx: u8,
+ display: SevenSegmentDisplay,
+}
+
+impl Iterator for SevenSegmentDisplayIterator {
+ type Item = bool;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.idx <= 7 {
+ let bit = self.display.get(self.idx);
+ self.idx += 1;
+ Some(bit)
+ } else {
+ None
+ }
+ }
+}
+
+impl IntoIterator for SevenSegmentDisplay {
+ type Item = bool;
+ type IntoIter = SevenSegmentDisplayIterator;
+
+ fn into_iter(self) -> Self::IntoIter {
+ SevenSegmentDisplayIterator {
+ idx: 0,
+ display: self,
+ }
+ }
+}
+
+// Einstiegspunkt für das Programm.
+fn main() {
+ let task_file_name = match env::args().nth(1) {
+ Some(x) => x,
+ None => {
+ eprintln!("Nutzung: aufgabe3 <dateiname>");
+ process::exit(1);
+ }
+ };
+ let task_str = fs::read_to_string(task_file_name).expect("Datei kann nicht gelesen werden");
+ let task = Task::try_from(task_str.as_str()).expect("Datei enthält keine gültige Aufgabe");
+
+ let start = time::Instant::now();
+ let solution = solve_task(task);
+ let elapsed = start.elapsed();
+
+ println!("{}", solution);
+ println!("Laufzeit ohne I/O: {:?}", elapsed);
+}
+
+// Liest eine Aufgabe im Format der Beispielaufgaben ein.
+impl TryFrom<&str> for Task {
+ type Error = ();
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ let mut lines = value.lines();
+ let number_str = lines.next().ok_or(())?;
+ let max_moves_str = lines.next().ok_or(())?;
+ let number = HexNumber::try_from(number_str)?;
+ let max_moves = usize::from_str(max_moves_str).map_err(|_| ())?;
+
+ Ok(Task { number, max_moves })
+ }
+}
+
+// Formatiert die Lösung zur Ausgabe.
+impl Display for Solution {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "Gegebene Hex-Zahl: {}", self.initial)?;
+ for state in &self.states {
+ state.fmt(f)?;
+ }
+ writeln!(f, "Größtmögliche Hex-Zahl: {}", self.r#final)?;
+ writeln!(f, "Gegebene Maximalzahl an Umlegungen: {}", self.max_moves)?;
+ writeln!(f, "Anzahl genutzter Umlegungen: {}", self.used_moves)?;
+ Ok(())
+ }
+}
+
+// Liest eine Hex-Ziffer aus einem ASCII-Zeichen.
+impl TryFrom<char> for HexDigit {
+ type Error = ();
+
+ fn try_from(c: char) -> Result<Self, Self::Error> {
+ match c {
+ '0' => Ok(Self(0x0)),
+ '1' => Ok(Self(0x1)),
+ '2' => Ok(Self(0x2)),
+ '3' => Ok(Self(0x3)),
+ '4' => Ok(Self(0x4)),
+ '5' => Ok(Self(0x5)),
+ '6' => Ok(Self(0x6)),
+ '7' => Ok(Self(0x7)),
+ '8' => Ok(Self(0x8)),
+ '9' => Ok(Self(0x9)),
+ 'A' => Ok(Self(0xA)),
+ 'B' => Ok(Self(0xB)),
+ 'C' => Ok(Self(0xC)),
+ 'D' => Ok(Self(0xD)),
+ 'E' => Ok(Self(0xE)),
+ 'F' => Ok(Self(0xF)),
+ _ => Err(()),
+ }
+ }
+}
+
+// Stellt eine Hex-Ziffer als ASCII-Zeichen dar.
+impl Display for HexDigit {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let char = match self.0 {
+ 0x0 => '0',
+ 0x1 => '1',
+ 0x2 => '2',
+ 0x3 => '3',
+ 0x4 => '4',
+ 0x5 => '5',
+ 0x6 => '6',
+ 0x7 => '7',
+ 0x8 => '8',
+ 0x9 => '9',
+ 0xA => 'A',
+ 0xB => 'B',
+ 0xC => 'C',
+ 0xD => 'D',
+ 0xE => 'E',
+ 0xF => 'F',
+ _ => unreachable!(),
+ };
+ f.write_char(char)
+ }
+}
+
+// Liest eine Hex-Zahl aus einem ASCII-String.
+impl TryFrom<&str> for HexNumber {
+ type Error = ();
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ let digits = value
+ .chars()
+ .map(|c| HexDigit::try_from(c))
+ .collect::<Result<Vec<HexDigit>, ()>>()?;
+
+ Ok(HexNumber { digits })
+ }
+}
+
+// Stellt eine Hex-Zahl als ASCII-String dar.
+impl Display for HexNumber {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for digit in &self.digits {
+ digit.fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+// Stellt eine Siebensegmentanzeige mithilfe der Zeichen des Unicode-Blocks
+// "Box Drawing" dar.
+impl SevenSegmentDisplay {
+ fn to_unicode(&self) -> [[char; 2]; 3] {
+ [
+ [
+ match (self.get(0), self.get(5)) {
+ (false, false) => ' ',
+ (false, true) => '╻',
+ (true, false) => '╺',
+ (true, true) => '┏',
+ },
+ match (self.get(0), self.get(1)) {
+ (false, false) => ' ',
+ (false, true) => '╻',
+ (true, false) => '╸',
+ (true, true) => '┓',
+ },
+ ],
+ [
+ match (self.get(4), self.get(5), self.get(6)) {
+ (false, false, false) => ' ',
+ (false, false, true) => '╺',
+ (false, true, false) => '╹',
+ (false, true, true) => '┗',
+ (true, false, false) => '╻',
+ (true, false, true) => '┏',
+ (true, true, false) => '┃',
+ (true, true, true) => '┣',
+ },
+ match (self.get(1), self.get(2), self.get(6)) {
+ (false, false, false) => ' ',
+ (false, false, true) => '╸',
+ (false, true, false) => '╻',
+ (false, true, true) => '┓',
+ (true, false, false) => '╹',
+ (true, false, true) => '┛',
+ (true, true, false) => '┃',
+ (true, true, true) => '┫',
+ },
+ ],
+ [
+ match (self.get(3), self.get(4)) {
+ (false, false) => ' ',
+ (false, true) => '╹',
+ (true, false) => '╺',
+ (true, true) => '┗',
+ },
+ match (self.get(2), self.get(3)) {
+ (false, false) => ' ',
+ (false, true) => '╸',
+ (true, false) => '╹',
+ (true, true) => '┛',
+ },
+ ],
+ ]
+ }
+}
+
+// Formatiert eine Reihe von Siebensegmentanzeigen mithilfe der Zeichen des
+// Unicode-Blocks "Box Drawing".
+impl Display for SevenSegmentDisplayRow {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let displays_as_unicode: Vec<[[char; 2]; 3]> = self
+ .displays
+ .iter()
+ .map(|display| display.to_unicode())
+ .collect();
+
+ for row_idx in 0..3 {
+ for unicode_digit in displays_as_unicode.iter() {
+ for char in unicode_digit[row_idx] {
+ write!(f, "{}", char)?;
+ }
+ }
+ writeln!(f)?;
+ }
+
+ Ok(())
+ }
+}