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, } // Beschreibt eine zu lösende Aufgabe. struct Task { number: HexNumber, max_moves: usize, } // Beschreibt den Zustand einer Siebensegmentanzeige. // Die Reihenfolge der Segmente entspricht dieser Abbildung: // https://en.wikipedia.org/wiki/Seven-segment_display#/media/File:7_Segment_Display_with_Labeled_Segments.svg #[derive(Clone, Copy)] struct SevenSegmentDisplay([bool; 7]); // Beschreibt den Zustand einer Reihe von Siebensegmentanzeigen. struct SevenSegmentDisplayRow { displays: Vec, } // 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, // 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, } } static mut BACKTRACK: usize = 0; // Implementiert den ersten Teil des Algorithmus, der aus einer Aufgabe die // größtmögliche Hex-Zahl errechnet. fn solve_pt1(task: &Task) -> Vec { // `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> { // 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 { unsafe { BACKTRACK += 1 }; 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()), _ => { unsafe { BACKTRACK += 1 }; None } }, Some((digit, rest)) => { // Die Anzahl der Stäbchen, aus der die erste Ziffer besteht. let digit_num_segments = digit.num_segments(); // `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_segments); // '1', die Ziffer bestehend aus den wenigsten Stäbchen, // enthält 2 Stäbchen. let new_occupied_segments_left = occupied_segments_left - (digit_num_segments - 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_segments as isize - candidate_digit.num_segments() 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. unsafe { BACKTRACK += 1 }; 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_segments = digit.num_segments(); ( acc_vacant_segments + (7 - num_segments), acc_occupied_segments + (num_segments - 2), ) }, ); let ret = solve_pt1_internal( &task.number.digits, 0, max_segment_flips, initial_vacant_segments, initial_occupied_segments, ) .unwrap(); println!("backtracks: {}", unsafe { BACKTRACK }); ret } // 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 { // 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::>(); let end_state = end .into_iter() .map(|digit| digit.to_seven_segments()) .collect::>(); // 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: usize, } 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 = Vec::new(); let mut unmatched_off_flips: Vec = 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::::new(); let mut off_flips = VecDeque::::new(); for (segment_idx, (segment_start, segment_end)) in segments_start .0 .iter() .zip(segments_end.0.iter()) .enumerate() { match (segment_start, segment_end) { (false, true) => on_flips.push_back(segment_idx), (true, false) => off_flips.push_back(segment_idx), _ => {} } } // `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].0[on_flip] = true; current_state[display_idx].0[off_flip] = false; 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].0[on_flip.segment_idx] = true; current_state[off_flip.display_idx].0[off_flip.segment_idx] = false; 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 => [true, true, true, true, true, true, false], 0x1 => [false, true, true, false, false, false, false], 0x2 => [true, true, false, true, true, false, true], 0x3 => [true, true, true, true, false, false, true], 0x4 => [false, true, true, false, false, true, true], 0x5 => [true, false, true, true, false, true, true], 0x6 => [true, false, true, true, true, true, true], 0x7 => [true, true, true, false, false, false, false], 0x8 => [true, true, true, true, true, true, true], 0x9 => [true, true, true, true, false, true, true], 0xA => [true, true, true, false, true, true, true], 0xB => [false, false, true, true, true, true, true], 0xC => [true, false, false, true, true, true, false], 0xD => [false, true, true, true, true, false, true], 0xE => [true, false, false, true, true, true, true], 0xF => [true, false, false, false, true, true, true], _ => unreachable!(), }; SevenSegmentDisplay(segments) } // Gibt die Anzahl der Stäbchen zurück, die für die Darstellung einer // Hex-Ziffer auf einer Siebensegmentanzeige benötigt werden. fn num_segments(self) -> usize { self.to_seven_segments() .0 .into_iter() .filter(|x| *x) .count() } // Gibt die Anzahl der "Stäbchen-Flips" zurück, die benötigt werden, um // von der Siebensegmentdarstellung einer Hex-Ziffer zu der einer anderen // zu gelangen. fn num_required_segment_flips(self, other: Self) -> usize { self.to_seven_segments() .0 .into_iter() .zip(other.to_seven_segments().0.into_iter()) .filter(|(segment_self, segment_other)| *segment_self != *segment_other) .count() } } // Einstiegspunkt für das Programm. fn main() { let task_file_name = match env::args().nth(1) { Some(x) => x, None => { eprintln!("Nutzung: aufgabe3 "); 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); println!("Gegebene Hex-Zahl: {}", &task.number); let start_pt1 = time::Instant::now(); let greatest_reachable_number = solve_pt1(&task); let elapsed_pt1 = start_pt1.elapsed(); println!( "Größtmögliche Hex-Zahl: {}", HexNumber { digits: greatest_reachable_number.clone(), } ); println!(); println!("Laufzeit (Teil 1): {:?}", elapsed_pt1); println!(); // let start_pt2 = time::Instant::now(); // let states = solve_pt2(&task.number.digits, &greatest_reachable_number); // let elapsed_pt2 = start_pt2.elapsed(); // println!("Zwischenstände:"); // for state in &states { // print!("{}", state); // } // println!(); println!("Gegebene Maximalzahl an Umlegungen: {}", task.max_moves); // println!("Anzahl genutzter Umlegungen: {}", states.len() - 1); // println!(); // println!("Laufzeit (Teil 2): {:?}", elapsed_pt2); // println!("Laufzeit (gesamt ohne I/O): {:?}", elapsed_pt1 + elapsed_pt2); } // Liest eine Aufgabe im Format der Beispielaufgaben ein. impl TryFrom<&str> for Task { type Error = (); fn try_from(value: &str) -> Result { 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 for HexDigit { type Error = (); fn try_from(c: char) -> Result { 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 { let digits = value .chars() .map(|c| HexDigit::try_from(c)) .collect::, ()>>()?; 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] { let s = self.0; [ [ match (s[0], s[5]) { (false, false) => ' ', (false, true) => '╻', (true, false) => '╺', (true, true) => '┏', }, match (s[0], s[1]) { (false, false) => ' ', (false, true) => '╻', (true, false) => '╸', (true, true) => '┓', }, ], [ match (s[4], s[5], s[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 (s[1], s[2], s[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 (s[3], s[4]) { (false, false) => ' ', (false, true) => '╹', (true, false) => '╺', (true, true) => '┗', }, match (s[2], s[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(()) } }