diff --git a/Aufgabe2-Rechenrätsel/doc.tex b/Aufgabe2-Rechenrätsel/doc.tex index 076a878..95a6323 100644 --- a/Aufgabe2-Rechenrätsel/doc.tex +++ b/Aufgabe2-Rechenrätsel/doc.tex @@ -63,82 +63,97 @@ \section{Lösungsidee} \subsection{Allgemeines} Der Beweis, ob ein Rechenrätsel eindeutig lösbar ist, ist ein -Entscheidungsproblem mit NP-Schwere. Eine Lösung ist eine interessante +Entscheidungsproblem mit NP-Schwere. Eine mögliche Lösung ist, eine interessante Ziffernfolge zufällig zu wählen und alle möglichen Kombinationen von Operatoren -auszuprobieren. Kommt ein Ergebnis nur einmal vor, wurde ein eindeutiges Rätsel -gefunden, was in der Regel der Fall sein sollte, ansonsten muss es mit anderen -Ziffern wiederholt werden. Theoretisch wäre es möglich, dass immer die Operanden -so zufällig so gewählt werden, dass es kein interessantes und eindeutiges Rätsel +auszuprobieren. Kommt ein Ergebnis nur einmal vor, wurde ein eindeutig lösbares +Rätsel gefunden, was in der Regel der Fall sein sollte, ansonsten muss der +Vorgang mit anderen Ziffern wiederholt werden. + +Theoretisch wäre es möglich, dass die Operanden immer +zufällig so gewählt werden, dass es kein interessantes und eindeutiges Rätsel gibt und das Programm somit nie zu einem Ergebnis kommt. Allerdings läuft die -Wahrscheinlichkeit dafür gegen null. Solch eine Brute-Force +Wahrscheinlichkeit dafür gegen null. + +Die beschriebene Brute-Force\footnote{Alle Möglichkeiten werden ausprobiert.} Operation dauert allerdings bei zunehmender Anzahl der Operatoren exponentiell länger. Die Anzahl der Möglichkeiten $|\Omega|$ kann in Abhängigkeit von der Operatorenanzahl $n$ berechnet werden: $|\Omega|=4^{n}$. Dabei gilt es zu beachten, dass dies der -Maximalwert ist, da die Division oft in Vorhinein ausgeschlossen werden kann, +Maximalwert ist, da die Division oft im Vorhinein ausgeschlossen werden kann, weil diese eine nicht ganze Zahl ergibt. Das Programm sollte Rätsel mit mindestens 15 Operatoren erstellen können, da dies in der Aufgabenstellung als Richtwert angegeben wird. Für $n=15$ gilt $|\Omega|=4^{15}=1073741824\approx10^{6}$. So viele Möglichkeiten können noch mit einer guten Laufzeit berechnet werden. -Eindeutigkeit auch mit ganzen Zwischenergebnissen? - \subsection{Die Null als Operand?} -Nach Aufgabenstellung ist jeder Operand nur eine Ziffer. Bei den Beispielen -kommt jede Ziffer vor außer die Null, allerdings wird auch nicht ausdrücklich -gesagt, dass man sie nicht verwenden darf. Die Addition und Subtraktion einer -Null kann in einem Rätsel nicht verwendet werden, da beide Operationen das -gleiche Ergebnis haben und das Rätsel damit uneindeutig wäre. Da durch null -Dividieren nicht definiert ist, bleibt nur noch die Multiplikation übrig. Weil -mit null Multiplizieren immer null ergibt, gilt auch für die Operatoren links -des Zwischenergebnisses, dass sie eine Multiplikation sein müssen. Dazu ein -Beispiel: Da bei $1\circ4\circ0$ das letzte \enquote{$\circ$} durch eine -Multiplikation ersetzt werden muss, also $1\circ4\cdot0=1\circ0$, muss auch das -erste \enquote{$\circ$} durch eine Multiplikation ersetzt werden. Da also die -Lösung für alle Operatoren, die sich links einer Null befinden, bekannt ist, -wären solche Rätsel langweilig, weshalb ich nur die Ziffern von 1 bis 9 für die -Rätsel verwende. +Nach Aufgabenstellung ist jeder Operand nur eine Ziffer. Bei den gegebenen +Beispielen kommt jede Ziffer vor außer der Null, allerdings wird auch nicht +ausdrücklich gesagt, dass man sie nicht verwenden darf. Die Addition und +Subtraktion einer Null kann in einem Rätsel nicht verwendet werden, da beide +Operationen das gleiche Ergebnis haben und das Rätsel damit uneindeutig wäre. Da +die Division durch Null nicht definiert ist, bleibt nur noch die Multiplikation +übrig. Es ergibt jedoch wenig Sinn ein Rätsel zu stellen, wo bereits zu Beginn +ein Teil der Lösung bekannt ist. Dies gilt insbesondere, da die Rätsel nach +Aufgabenstellung \enquote{richtig schwer} sein sollen. Also wird die Null in +meiner Lösung nicht als Operand benutzt. -\subsection{interessante Rätsel} -Die Aufgabenstellung gibt nicht genau vor wie ein Rätsel, das \enquote{interessant und +\subsection{Interessante Rätsel} +Die Aufgabenstellung gibt nicht genau vor, wie ein Rätsel, das \enquote{interessant und unterschiedlich} ist, zu sein hat. Allerdings wird ein Beispiel gegeben, wie ein -Rätsel aussehen kann. Das Beispielrätsel habe ich mal lösen lassen: +Rätsel aussehen kann. Das Beispielrätsel habe ich mit einer leicht modifizierten +Version meines Programms lösen lassen: \begin{minted}{text} Rätsel: 4 ○ 3 ○ 2 ○ 6 ○ 3 ○ 9 ○ 7 ○ 8 ○ 2 ○ 9 ○ 4 ○ 4 ○ 6 ○ 4 ○ 4 ○ 5 = 4792 Lösung: 4 * 3 * 2 * 6 * 3 * 9 + 7 * 8 : 2 * 9 * 4 - 4 * 6 - 4 * 4 * 5 = 4792 \end{minted} -Es fällt auf das jeder Operator mindestens einmal vorkommt, wobei Multiplikation -überwiegt. Außerdem ist das Ergebnis noch relativ klein im Vergleich zu anderen -eindeutig lösbaren Rätseln. Zudem kommen einige verschiedene Ziffern vor. -Meine Regeln für ein interessantes Rätsel sind noch etwas strenger, da hier -schon recht oft Multiplikation vertreten ist. Diese lauten wie folgt: +Es fällt auf das jeder Operator mindestens einmal vorkommt, wobei die +Multiplikation überwiegt. Außerdem ist das Ergebnis noch relativ klein im +Vergleich zu anderen eindeutig lösbaren Rätseln, wie ich beim Vergleich mit der +ungefilterten Ausgabe meines Programms festgestellt habe. Zudem kommen einige +verschiedene Ziffern vor. Meine Regeln für ein interessantes Rätsel sind noch +etwas strenger, da in dem Beispiel schon recht oft die Multiplikation vertreten +ist. Diese lauten wie folgt: \begin{enumerate} - \item Bei $n$ Operatoren muss für $m_i$, die Anzahl, mit der jeder der - vier Operatoren vorkommt, gelten: + \item Sei $n$ die Anzahl der Operatoren und $m_i$, die Anzahl, mit der + der jeweilige Operator vorkommt. Dann soll gelten: \begin{align} \frac{n}{10} - 1 < m_i \leq \frac{n}{2} + 1 \end{align} - \item Bei $n$ Ziffern muss für $m_i$, die Anzahl, mit der jede der neun - Ziffern vorkommt, gelten: + Die Division ist oft nicht möglich, da das Ergebnis keine ganze + Zahl ist. Deshalb muss die Untergrenze relativ klein sein, damit + für die Division die Bedingung noch erfüllt werden kann. Die + Formel wurde so gewählt, dass bei Rätsel mit zwei Operatoren + noch zweimal derselbe Operator gewählt werden darf. Allerdings + finde ich es bei Rätseln mit drei Operatoren nicht interessant, + jedes Mal denselben Operator zu haben. Darum habe ich die Formel + so gewählt, dass in diesem Fall die Obergrenze nicht erfüllt + ist. + \item Sei $n$ die Anzahl der Ziffern und $m_i$, die Anzahl mit der die + jeweilige Ziffer vorkommt. Dann soll gelten: \begin{align} - \frac{n}{16} - 1 < m_i \leq \frac{n}{4} + 1 + \frac{n}{18} - 1 < m_i \leq \frac{2}{9} \cdot n + 1 \end{align} + Für diese Untergrenze habe ich mich entschieden, damit, wenn + durchschnittlich jede Ziffer der neun Ziffern zweimal vorkommen sollte, + mindestens eine vorhanden ist. Aus einem ähnlichen Grund habe + ich auch die Obergrenze so gewählt, dass $\lim_{n \to \infty} + \frac{2}{9} \cdot n + 1 = \lim_{n \to \infty} \frac{2}{9} \cdot n$ \end{enumerate} In der Regel werden viele interessante Rätsel gefunden. Da das Beispielrätsel ein kleines Ergebnis hat und kleinere Ergebnisse auch eleganter sind, wird -von allen interessanten Rätsel, das mit dem kleinsten Ergebnis ausgewählt. +von allen interessanten Rätseln, das mit dem kleinsten Ergebnis ausgewählt. \section{Umsetzung} \subsection{Umgebung und Bibliotheken} Die Lösungsidee wird in Rust nightly implementiert, da Trait Aliase noch nicht in Rust stable sind. Jeder Typ, der ein Trait implementiert, muss bestimmte -Methode haben. Traits sind daher für Generics essenziell. Trait Aliase machen es +Methoden haben. Traits sind daher für Generics essenziell. Trait Aliase machen es nun möglich für mehrere Traits einen Alias zu erstellen, was Schreibarbeit -spart. Außerdem werden ein paar crates, also Abhängigkeiten in Rust, verwendet: +spart. Außerdem werden einige crates, also Abhängigkeiten in Rust, verwendet: \begin{itemize} \item Mit rand werden zufällige Ziffern als Operanden erstellt. \item Mit clap werden die Kommandozeilenargumente verarbeitet. - \item Außerdem werden noch ein paar crates des rust-num Teams genutzt. + \item Zudem werden noch einige crates des rust-num Teams genutzt. Dabei wird nicht das meta-crate num verwendet, sondern die sub-crates werden einzeln verwendet, um die Abhängigkeiten kleinzuhalten. num-traits ist dabei u. a. für die Konvertierung @@ -152,45 +167,44 @@ spart. Außerdem werden ein paar crates, also Abhängigkeiten in Rust, verwendet \subsection{Benutzung} Mit der Option -c kann die Anzahl der Operatoren angegeben werden. Der Standartwert ist 5. Allerdings können maximal Rätsel mit 32 Operatoren berechnet -werden, da die verwendeten Operatoren eines Rätsel in 64 Bit Integers +werden, da die verwendeten Operatoren eines Rätsels in 64 Bit Integers gespeichert werden. Da es vier Grundrechenarten gibt brauchen wir für jeden Operator 2 Bits, um diesen darzustellen. Da $64/2=32$ können also maximal 32 Operatoren verwendet werden. Mit dem flag -s kann man die Ausgabe der Lösung zu dem Rätsel einschalten. Schließlich können optional mit -d die Operanden angegeben werden, z. -B. : \mintinline{text}|-d={1,4,5,8}|. Dies ist nur zu Testzecken, wenn man die +B. : \mintinline{text}|-d={1,4,5,8}|. Dies ist sehr nützlich zu Testzwecken. Wenn man die Operanden nicht angibt, werden automatisch interessante gewählt. \subsection{Implementierungsart} In der main-Funktion befindet sich direkt eine while-Schleife, die solange läuft bis ein interessantes Rätsel gefunden wurde. In der Regel wird die Schleife nur einmal -durchlaufen, weil für die erste ausgewählte Operandenkombination normalerweise -auch ein interessantes und eindeutiges Rätsel gibt. Wenn der Nutzer keine +durchlaufen, weil sich für die erste ausgewählte Operandenkombination normalerweise +auch ein interessantes und eindeutiges Rätsel ergibt. + +Wenn der Nutzer keine Operanden angibt, werden diese zufällig ausgewählt. Dabei befinden sich die -Ziffern in einem Vector (Array mit dynamischer Größe) und werden daraus +Ziffern in einem Vector\footnote{Implementierung von Arrays mit +dynamischer Größe aus Rusts Standartbibliothek} und werden daraus zufällig gewählt. Damit die minimale und maximale Anzahl aller Ziffern garantiert werden kann, sodass das Rätsel interessant ist, werden in Laufe der Auswahl Ziffern aus dem Vector entfernt. -Danach wird die Funktion \mintinline{rs}|calc_results| mit den Operanden +Danach wird die Funktion \mintinline{rs}|calc_results| mit diesen Operanden aufgerufen. Diese kann durch Generics alle Integertypen für die (Zwischen)ergebnisse verwenden. Das größte Ergebnis für die Operanden wird berechnet, indem sie alle miteinander multipliziert werden. Wenn dieses zu groß ist für 64 Bit Integers, werden 128 Bit Integers verwendet, ansonsten 64 Bit Integers. Dafür habe ich mich entschieden, da mit 64 Bit Rechnern schneller mit -64 Integers gerechnet werden kann. Jedoch kann man trotzdem Rätsel berechnen, -die 128 Bit Integers erfordern. Man hätte auch noch kleinere Rätsel mit 32 Bit -Integers berechnen können, um das Programm für 32 Bit Rechner zu optimieren. -Allerdings werden auch für die Operatoren 64 Bit Integers verwendet, was man -dann auch noch generisch implementierten hätte müssen. Außerdem sind 32 Bit PCs -schon mindestens 15 Jahre veraltet, weshalb mittlerweile eigentlich jeder einen -64 Bit PC haben sollte. +64 Bit Integers gerechnet werden kann. Jedoch kann man trotzdem Rätsel berechnen, +die 128 Bit Integers erfordern. -Innerhalb der Funktion ist eine for-Schleife, die über alle Möglichkeiten -Operanden mit Punkt- bzw. Strichrechnung zu wählen iteriert. Dabei wird die -Variable \mintinline{rs}|dm_as_map| nach jedem Durchlauf um eins erhöht. Jedes -Bit der Variable steht dabei für Punkt- oder Strichrechnung, wobei das least -significant Bit für den Operatoren ganz links steht. +Innerhalb der Funktion \mintinline{rs}|calc_results| gibt es eine for-Schleife, +die über alle Möglichkeiten, Operanden mit Punkt- bzw. Strichrechnung zu wählen, +iteriert. Dabei wird die Variablen \mintinline{rs}|dm_as_map| nach jedem +Durchlauf um eins erhöht. Jedes Bit der Variable steht dabei für Punkt- oder +Strichrechnung, wobei das least significant Bit für den Operatoren ganz links +steht. In der Schleife wird die Funktion \mintinline{rs}|multiplicate_divide| mit einer Sequenz von Operanden, zwischen denen nur Punktrechnung vorkommt, aufgerufen. Diese ruft @@ -207,38 +221,417 @@ in den Vector übernommen, indem eine Hashmap mit nur einem möglichen Zwischenergebnis erstellt wird. In der rekursiven Funktion \mintinline{rs}|add_sub| wird mit einer for-Schleife -über alle möglichen Zwischenergebnissen iteriert. Innerhalb der Schleife ruft +über alle möglichen Zwischenergebnisse iteriert. Innerhalb der Schleife ruft sich die Funktion pro Durchlauf zweimal auf, wobei einmal addiert und einmal -subtrahiert wird. Dies passiert solange bis die letzte Hashmap mit möglichen +subtrahiert wird. Dies passiert solange, bis die letzte Hashmap mit möglichen Ergebnissen erreicht wurde. Dann wird das Ergebnis mit den verwendeten Operatoren mithilfe der Struktur \mintinline{rs}|ResultStore| gespeichert, wobei -es auch sein kann, das das Ergebnis direkt als uneindeutig markiert wird, da +es auch passieren kann, dass das Ergebnis direkt als uneindeutig markiert wird, da schon eines der Zwischenergebnisse uneindeutig war. Die Struktur \mintinline{rs}|ResultStore| besteht aus einer Hashmap mit den Operatoren als Wert und den dazugehörigen Ergebnissen als Schüssel. Außerdem hat -sie noch ein weiters Attribut, das ein Hashset mit den Ergebnissen für das -Rätsel uneindeutig wäre. Die Methode \mintinline{rs}|store| hat als Parameter +sie noch ein weiters Attribut, ein Hashset, mit den Ergebnissen für die das +Rätsel uneindeutig ist. Die Methode \mintinline{rs}|store| hat als Parameter das Ergebnis und eine Option von Operatoren. Wenn die Option \mintinline{rs}|None| ist, wird das Ergebnis in das Hashset der uneindeutigen Ergebnisse aufgenommen. Wenn das Ergebnis weder in der Hashmap noch in dem Hashset ist, wird das Rätsel in der Hashmap gespeichert. Wenn es bereits in der Hashmap ist, wird es als uneindeutig im Hashset gespeichert. -Schließlich wird über alle Schüssel Werte Paare der Hashmap einer Instanz der +Schließlich wird über alle Schlüssel-Werte-Paare der Hashmap einer Instanz der \mintinline{rs}|ResultStore| Struktur iteriert. Es wird gezählt wie oft welche Operatoren verwendet wurden und überprüft, ob die Anzahl innerhalb des Bereiches -liegt, indem das Rätsel als interessant befunden wird. Von allen interessanten -und uneindeutigen Rätseln wird bestimmt welches das kleinste Ergebnis hat und -diese wird ausgegeben. Wenn kein interessantes Rätsel gefunden wurde, muss die +liegt, in dem das Rätsel als interessant befunden wird. Von allen interessanten +und uneindeutigen Rätseln wird bestimmt, welches das kleinste Ergebnis hat, und +dieses wird ausgegeben. Wenn kein interessantes Rätsel gefunden wurde, muss die while-Schleife der main-Funktion noch einmal durchlaufen werden. \section{Beispiele} +Im Ordner \enquote{ergebnisdateien} findet man die Beispielausgaben nummeriert +nach der Operatorenanzahl. + \input{|./ergebnis-latex.sh} \section{Quellcode} -Unwichtige Teile des Programms sollen hier nicht abgedruckt werden. Dieser Teil sollte nicht mehr als 2–3 Seiten umfassen, maximal 10. +\begin{minted}[breaklines,fontsize=\footnotesize]{rs} +#![feature(trait_alias)] // aktiviert Trait Aliase + +// Importe +use clap::Parser; +use std::collections::{HashSet, HashMap}; +use rand::distributions::{Distribution, Uniform}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use num_integer::Integer; +use std::hash::Hash; +use std::fmt::Display; +use num_traits::bounds::Bounded; + +// definiert einen Trait Alias, da diese Traits öfter gemeinsam benötigt werden +trait BasicInteger = Integer + Hash + Copy; + +// Enum für die vier Grundoperatoren +#[derive(FromPrimitive)] // Durch FromPrimitive können ganze Zahlen zu Enums konvertiert werden. +enum Operator { + Add, + Subtract, + Multiplicate, + Divide, +} + +// Enum für Punkt- oder Strichrechnung +#[derive(FromPrimitive, PartialEq, Clone, Copy)] +enum AddSubOrMultiplicateDivide { + AddSub, + MultiplicateDivide, +} + +// Struktur für die Kommandozeilenargumente +#[derive(Parser)] +struct Args { + /// Anzahl der Operatoren + #[clap(short, long, default_value_t = 5)] + count: u8, + + /// zeigt die Lösung an + #[clap(short, long)] + solution_print: bool, + + /// nutzt vorgegebene Ziffern zur Erstellung des Rätsels (für reproduzierbare Testfälle) + #[clap(short, long)] + digits: Vec, +} + +struct PartOperation { + + // mögliche Zwischenergebnisse eines Teils der Rechnung, der nur Punktrechnung enthält. + results: HashMap>, + last_operator: Option, // der Operator, der vor dieser Rechnungsteil steht +} + +// Struktur, die eindeutige Ergebnisse speichert +struct ResultStore { + riddles: HashMap, // speichert eindeutige Rätsel + + // speichert Ergebnisse, für die es bereits mehrere Operatorenkombinationen gibt + results_already_taken: HashSet, +} + +impl ResultStore { + + // erstellt einen neuen, leere ResultStore + fn new() -> Self { + Self { + riddles: HashMap::new(), + results_already_taken: HashSet::new(), + } + } + + /* Methode, zum Speichern eines neuen Ergebnisses. + * Ist der Parameter operators == None, so ist das Ergebnis uneindeutig. */ + fn store(&mut self, result: T, operators: Option) { + + // Wenn das Ergebnis bereits uneindeutig ist, wird nichts gemacht. + if self.results_already_taken.contains(&result) {return;} + + // Wenn es das Ergebnis bereits gibt, ... + if match operators { + None => true, + Some(_x) => self.riddles.contains_key(&result), + } { + // ... dann wird es von den eindeutigen Rätseln entfernt ... + self.riddles.remove(&result); + self.results_already_taken.insert(result); // ... und zu den uneindeutigen hinzugefügt. + } + // Ist es eindeutig, wird zu den eindeutigen hinzugefügt. + else {self.riddles.insert(result, operators.unwrap());} + } +} +fn main() { + // Methode des crates clap, die die Kommandozeilenargumente einliest. + let args = Args::parse(); + + /* Rätsel für größere Operatorenanzahlen können nicht berechnet werden, da die verwendeten + * Operatoren als u64 gespeichert werden müssen und für jeden Operator zwei Bits benötigt + * werden. Natürlich ist die praktische Grenze laufzeitbedingt schon vorher erreicht. */ + if args.count > 32 { + println!("Es können maximal Rätsel mit 32 Operatoren erstellt werden."); + return; + } + let mut continue_searching = true; + let mut digits = args.digits; + + // Wenn keine Ziffern bzw. Operanden vorgegeben wurden, werden diese zufällig ausgewählt. + let random_digits = digits.is_empty(); + while continue_searching { + if random_digits { + digits = vec!(0; (args.count + 1) as usize); // erstellt einen neuen Vektor für die Ziffern + + /* die Ziffern, die noch zur Auswahl stehen. Dabei steht jedes Element des Vektors für + * die Ziffer, die um eins größer ist. */ + let mut selectable_digits: Vec = Vec::from_iter(0..9); + let mut digits_count: [u8; 9] = [0; 9]; // Counter wie oft die jeweiligen Ziffern vorkommen + let min: u8 = digits.len() as u8 / 18; // minmale Anzahl der jeweiligen Ziffern + let max: u8 = digits.len() as u8 * 2 / 9 + 1; // maximale Anzahl + + /* Anzahl der Operanden, die verwendet werden können ohne, dass dadurch der Erfüllung + * der minimalen Anzahl der Ziffern nähergekommen wird. */ + let mut left_for_non_min: u8 = digits.len() as u8 - 9 * min; + + /* die Ziffern, die bereits in der minimalen Anzahl vorhanden sind. + * Es sei denn die minimale Anzahl ist Null, dann ist das Hashset leer. */ + let mut digits_min_statisfied: HashSet = HashSet::with_capacity(9); + for digit in digits.iter_mut() { + + // Eine zufällige Ziffer wird aus den übrig bleibenden ausgewählt. + let rand_i = Uniform::new(0, selectable_digits.len()).sample(&mut rand::thread_rng()); + let rand_number = selectable_digits[rand_i]; + + digits_count[rand_number as usize] += 1; // erhöht den Zifferncounter + let digit_used = digits_count[rand_number as usize]; + if digit_used == min { + if left_for_non_min == 0 { // Wenn keine Operanden frei sind, ... + selectable_digits.remove(rand_i); // ... wird die Ziffer aus den ausgewählbaren entfernt. + } else {digits_min_statisfied.insert(rand_number);} + } + if digit_used > min { // wird die Ziffern öfter als notwendig verwendet + left_for_non_min -= 1; + + /* Wenn die restlichen Operanden gebraucht werden, um die minimale Anzahl der + * übrigen Ziffern zu erfüllen, werden die Ziffern die bereits genug vorkommen, + * aus den auswählbaren entfernt. */ + if left_for_non_min == 0 { + selectable_digits.retain(|x| !digits_min_statisfied.contains(x)); + } + } + // Kommt eine Ziffer oft genug vor, wird diese aus den auswählbaren entfernt. + if digit_used == max {selectable_digits.remove(rand_i);} + + /* weist die zufällig ausgewählte Ziffer zu. Diese wird zuvor noch um eins erhöht, + * da die Zufallszahl im Bereich von null bis acht liegt. */ + *digit = rand_number + 1; + } + } + // berechnet das maximale Ergebnis durch die Multiplikation aller Operanden + let mut max_result: u128 = 1; + for digit in &digits { + max_result *= *digit as u128; + } + /* nutzt 64 Bit Integers wenn möglich und 128 Bit Integers das maximale Ergebnis zu groß für + * 64 Bit Integers ist. */ + continue_searching = if max_result <= i64::MAX as u128 { + calc_results::(args.solution_print, &digits) + } else {calc_results::(args.solution_print, &digits)}; + + // Wenn die Ziffern vom Nutzer festgelegt sind, wird es nicht nochmal mit anderen versucht. + if !random_digits {break;} + } +} + +/* Funktion, die eindeutige Rätsel mit den vorgegebenen Operanden berechnet und eins davon ausgibt. + * Dabei kann der Integer-Typ der für die (Zwischen)ergebnisse verwendet mit Generics angegeben werden. */ +fn calc_results +(solution_print: bool, digits: &[u8]) -> bool { + let number_operators = digits.len() - 1; + let mut results = ResultStore::new(); // Hier werden die Rätsel gespeichert. + + // Bei dm_as_map steht jedes Bit für Punkt- oder Strichrechnung. + for dm_as_map in 0..2u32.pow(number_operators as u32) { + + // enthält die Zwischenergebnisse mit bereits durchgeführter Punktrechnung + let mut results_multiplicate: Vec> = Vec::new(); + { + let mut last_i: usize = 0; + let mut i: usize; + let mut state = AddSubOrMultiplicateDivide::from_u32(dm_as_map & 1).unwrap(); + while last_i < number_operators { + i = last_i; + + // bestimmt das Ende einer Sequenz, die nur aus Punkt- oder Strichrechnung besteht + while i < number_operators && dm_as_map >> i & 1 == state as u32 { + + /* In einer Strichrechnungssequenz können die Operanden direkt als + * Zwischenergebnisse verwendet werden. */ + if state == AddSubOrMultiplicateDivide::AddSub { + results_multiplicate.push(insert_digit(digits, i)); + } + i += 1; + } + if state == AddSubOrMultiplicateDivide::MultiplicateDivide { + + // speichert die Ergebnisse einer Punktrechnungssequenz + let mut part_results: HashMap> = HashMap::new(); + i += 1; + let mut digits_calc = digits[last_i .. i].iter(); // Slice mit den Operanden der Sequenz + + // der erste Operand der Sequenz + let first_digit = T::from_u8(*digits_calc.next().unwrap()).unwrap(); + + // berechnet die Zwischenergebnisse + multiplicate_divide(digits_calc, &mut part_results, last_i as u8 * 2, first_digit, 0); + + // speichert die Zwischenergebnisse + results_multiplicate.push(PartOperation{ + results: part_results, + last_operator: last_operator_helper(last_i), + }); + } + /* Nachdem eine Punkt- oder Strichrechnungssequenz fertig, ist kommt eine Sequenz + * der anderen Rechenart. */ + state = match state { + AddSubOrMultiplicateDivide::MultiplicateDivide => AddSubOrMultiplicateDivide::AddSub, + AddSubOrMultiplicateDivide::AddSub => AddSubOrMultiplicateDivide::MultiplicateDivide, + }; + last_i = i; + } + /* Wenn der letzte Operator eine Strichrechnung ist, muss der letzte Operand noch als + * Zwischenergebnis hinzugefügt werden. */ + if dm_as_map >> number_operators - 1 & 1 == AddSubOrMultiplicateDivide::AddSub as u32 { + results_multiplicate.push(insert_digit(digits, digits.len() - 1)); + } + { + // führt die Strichrechnung mit den Zwischenergebnissen durch + let mut iter = results_multiplicate.iter(); + for (part_result, operators) in &iter.next().unwrap().results { + add_sub(iter.clone(), &mut results, *part_result, *operators); + } + } + } + } + let mut best_riddle_operators: Option = None; // die Operatoren des endgültig ausgewählten Rätsel + + /* das Ergebnis des ausgewählten Rätsels. Zu Beginn wird der maximale Wert verwendet, damit die + * Bedingung, dass das Ergebnis des Rätsels kleiner ist, in beim ersten Rätsel erfüllt ist. */ + let mut smallest_result = T::max_value(); + for (result, operators) in results.riddles { + let mut operator_count: [u8; 4] = [0; 4]; // Anzahlen der Operatoren + + // zählt die Operatoren + for i in 0..number_operators { + operator_count[(operators as usize) >> 2 * i & 3] += 1; + } + + /* überprüft, ob alle Operatorenanzahlen innerhalb eines Bereiches liegen und das Rätsel + * somit interessant ist. */ + let mut interessing_riddle = true; + for count in operator_count { + interessing_riddle &= number_operators as u8 / 10 <= count && count <= number_operators as u8 / 2 + 1; + } + + /* Ist das Rätsel interessant und das Ergebnis kleiner als das aktuelle Rätsel, dann wird + * dieses als vorläufig bestes Rätsel gespeichert. */ + if interessing_riddle && result < smallest_result { + smallest_result = result; + best_riddle_operators = Some(operators); + } + } + match best_riddle_operators { + /* wurde kein Rätsel gefunden wird true zurückgeben und die Suche wird wiederholt, da der + * Rückwert als Bedingung in der while-Schleife genutzt wird. */ + None => true, + Some(operators) => { + // Wenn ein Rätsel gefunden wurde, wird es ausgegeben. (siehe Quelltext) + false + } + } +} + +/* rekursive Funktion, die Punktrechnung auf einer Sequenz der Operanden durchführt und die + * Ergebnisse in einer Hashmap speichert. */ +fn multiplicate_divide<'a, T: BasicInteger + FromPrimitive> +(mut iter: impl Clone + Iterator, results: &mut HashMap>, +operator_index: u8, part_result: T, operators: u64) { + match iter.next() { + /* Wenn keine Ziffern mehr übrig sind, werden die Operatoren in einer Hashmap abspeichert. + * Wenn das gleiche Zwischenergebnis bereits durch andere Operatoren erreicht wurde ist + * dieses uneindeutig und None wird in der Hashmap gespeichert. */ + None => { + results.insert(part_result, if results.contains_key(&part_result) + {None} else {Some(operators)}); + } + Some(next) => { + let next_digit = T::from_u8(*next).unwrap(); + + // führt Multiplikation und Division durch + for operator in [Operator::Multiplicate, Operator::Divide] { + multiplicate_divide(iter.clone(), results, operator_index + 2, + match operator { + Operator::Multiplicate => part_result * next_digit, + Operator::Divide => { + /* Ist das Ergebnis der Division keine ganze Zahl, wird diese + * abgebrochen. */ + if part_result \% next_digit != T::zero() {continue;} + part_result / next_digit + } + /* wird nie ausgeführt. Nur vorhanden, da bei einer match-Anweisung + * alle möglichen Werte abgedeckt werden müssen. */ + _ => T::zero(), // Methode returned eine Null des Typs T + }, + // verwendeter Operator wird in einer Bitmap gespeichert. + operators | (operator as u64) << operator_index); + } + } + } +} + +/* rekursive Funktion, die Strichrechnung auf einer Sequenz der Zwischenergebnisse durchführt und + * die Ergebnisse mithilfe der Struktur ResultStore speichert. */ +fn add_sub<'a, T: BasicInteger + 'static> +(mut iter: impl Clone + Iterator>, +results: &mut ResultStore, part_result: T, operators: Option) { + match iter.next() { + None => { + // speichert das Rätsel, wenn das Ergebnis eine natürliche Zahl ist + if part_result > T::zero() {results.store(part_result, operators);} + } + Some(next) => { + // für jedes mögliche Zwischenergebnis ... + for (next_result, part_operators) in &next.results { + // ... wird Addition und Subtraktion durchführt. + for operator in [Operator::Add, Operator::Subtract] { + add_sub(iter.clone(), results, + match operator { + Operator::Add => part_result + *next_result, + Operator::Subtract => part_result - *next_result, + + /* wird nie ausgeführt. Nur vorhanden, da bei einer match-Anweisung + * alle möglichen Werte abgedeckt werden müssen. */ + _ => T::zero(), + }, + /* Wenn mindestens einer der beiden verrechneten Zwischenergebnisse + * uneindeutig ist, ist das resultierende Ergebnis auch uneindeutig. */ + if operators == None || *part_operators == None {None} + else { + // Ansonsten werden die Teiloperatoren zusammengefasst gespeichert. + Some(operators.unwrap() | part_operators.unwrap() | + (operator as u64) << 2 * next.last_operator.unwrap()) + } + ); + } + } + } + } +} + +// gibt den Operatorenindex vor dem entsprechenden Operandenindex zurück +fn last_operator_helper(index: usize) -> Option { + if index == 0 {None} else {Some(index as usize - 1)} +} + +// Hilfsfunktion, um eine einzelne Ziffer als Zwischenergebnis hinzufügen +fn insert_digit(digits: &[u8], i: usize) -> PartOperation { + let mut result_map: HashMap> = HashMap::new(); + + // Some(0) heißt, dass keine Operatoren verwendet wurden + result_map.insert(T::from_u8(digits[i]).unwrap(), Some(0)); + PartOperation{ + results: result_map, + last_operator: last_operator_helper(i), + } +} +\end{minted} \end{document} diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/23.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/23.txt new file mode 100644 index 0000000..222bb10 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/23.txt @@ -0,0 +1,2 @@ +Rätsel: 8 ○ 5 ○ 5 ○ 6 ○ 6 ○ 3 ○ 5 ○ 4 ○ 5 ○ 5 ○ 3 ○ 2 ○ 4 ○ 6 ○ 6 ○ 2 ○ 2 ○ 3 ○ 8 ○ 8 ○ 2 ○ 9 ○ 7 ○ 1 = 362504 +Lösung: 8 + 5 + 5 + 6 * 6 * 3 * 5 * 4 * 5 * 5 * 3 : 2 : 4 * 6 * 6 : 2 - 2 + 3 - 8 * 8 : 2 * 9 * 7 + 1 = 362504 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/24.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/24.txt new file mode 100644 index 0000000..2df3817 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/24.txt @@ -0,0 +1,2 @@ +Rätsel: 1 ○ 5 ○ 1 ○ 8 ○ 5 ○ 5 ○ 3 ○ 2 ○ 2 ○ 8 ○ 3 ○ 5 ○ 7 ○ 8 ○ 7 ○ 6 ○ 9 ○ 1 ○ 8 ○ 2 ○ 1 ○ 1 ○ 8 ○ 4 ○ 9 = 5953764 +Lösung: 1 - 5 - 1 + 8 * 5 * 5 * 3 : 2 : 2 * 8 * 3 * 5 * 7 : 8 * 7 * 6 * 9 - 1 - 8 * 2 - 1 - 1 + 8 * 4 * 9 = 5953764 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/25.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/25.txt new file mode 100644 index 0000000..ad1378b --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/25.txt @@ -0,0 +1,2 @@ +Rätsel: 8 ○ 9 ○ 1 ○ 9 ○ 9 ○ 6 ○ 8 ○ 3 ○ 8 ○ 7 ○ 2 ○ 7 ○ 4 ○ 5 ○ 9 ○ 6 ○ 4 ○ 6 ○ 2 ○ 5 ○ 8 ○ 1 ○ 9 ○ 9 ○ 4 ○ 5 = 1787641 +Lösung: 8 - 9 - 1 - 9 - 9 + 6 * 8 * 3 : 8 * 7 : 2 * 7 * 4 * 5 * 9 * 6 : 4 * 6 : 2 * 5 - 8 - 1 + 9 * 9 * 4 * 5 = 1787641 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/26.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/26.txt new file mode 100644 index 0000000..4267e51 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/26.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 5 ○ 8 ○ 6 ○ 4 ○ 5 ○ 8 ○ 4 ○ 7 ○ 4 ○ 9 ○ 8 ○ 7 ○ 5 ○ 2 ○ 1 ○ 5 ○ 7 ○ 1 ○ 4 ○ 8 ○ 4 ○ 9 ○ 2 ○ 6 ○ 1 ○ 3 = 22119216 +Lösung: 2 - 5 + 8 * 6 * 4 * 5 * 8 * 4 * 7 * 4 * 9 * 8 : 7 * 5 : 2 - 1 - 5 * 7 - 1 - 4 + 8 * 4 * 9 : 2 * 6 - 1 - 3 = 22119216 diff --git a/Aufgabe2-Rechenrätsel/src/main.rs b/Aufgabe2-Rechenrätsel/src/main.rs index 310b8f2..2572f83 100644 --- a/Aufgabe2-Rechenrätsel/src/main.rs +++ b/Aufgabe2-Rechenrätsel/src/main.rs @@ -117,8 +117,8 @@ fn main() { * die Ziffer, die um eins größer ist. */ let mut selectable_digits: Vec = Vec::from_iter(0..9); let mut digits_count: [u8; 9] = [0; 9]; // Counter wie oft die jeweiligen Ziffern vorkommen - let min: u8 = digits.len() as u8 / 16; // minmale Anzahl der jeweiligen Ziffern - let max: u8 = digits.len() as u8 / 4 + 1; // maximale Anzahl + let min: u8 = digits.len() as u8 / 18; // minmale Anzahl der jeweiligen Ziffern + let max: u8 = digits.len() as u8 * 2 / 9 + 1; // maximale Anzahl /* Anzahl der Operanden, die verwendet werden können ohne, dass dadurch der Erfüllung * der minimalen Anzahl der Ziffern nähergekommen wird. */ diff --git a/Aufgabe3-HexMax/doc.tex b/Aufgabe3-HexMax/doc.tex index a160c69..91ffdcc 100644 --- a/Aufgabe3-HexMax/doc.tex +++ b/Aufgabe3-HexMax/doc.tex @@ -45,6 +45,13 @@ %\usepackage{hyperref} % Anklickbare Links im Dokument \usepackage{cleveref} +\usepackage{forloop} + +\newcommand{\beispiel}[1] { + hexmax#1.txt: + \input{|target/debug/hexmax -s -l -d 17 beispieldaten/hexmax#1.txt} +} + % Daten für die Titelseite \title{\textbf{\Huge\Aufgabe}} \author{\LARGE Teilnahme-ID: \LARGE \TeilnahmeId \\\\ @@ -60,48 +67,191 @@ \vspace{0.5cm} \section{Lösungsidee} -Durch ebenso oftes Hinlegen und Wegnehmen der Stäbchen erhält man eine Hex-Zahl, -die man auch mit Umlegen erreichen kann. Der Algorithmus kann also erstmal mehr +Durch ebenso häufiges Hinlegen und Wegnehmen der Stäbchen erhält man eine Hex-Zahl, +die man auch durch Umlegen der Stäbchen erreichen kann. Der Algorithmus kann also erstmal mehr Stäbchen hinlegen wie wegnehmen oder umgekehrt, solange am Ende die Anzahl der -hingelegten Stäbchen gleich der weggenommenen Stäbchen ist. +hingelegten Stäbchen gleich der der weggenommenen Stäbchen ist. Die größte Zahl erhält man, wenn man möglichst die vorderen Ziffern erhöht. Der -Algorithmus geht angefangen mit der ersten Ziffer jede Ziffer der Zahl durch. -Ausgenommen sind davon Ziffern, die schon geändert wurden. -Jede Ziffer wird, sofern sie nicht schon F ist, probiert auf F,E,... umzulegen. -Dabei wird gezählt wie viele Stäbchen weggenommenen und hingelegt werden müssen. -Überschreitet weder die Gesamtzahl der weggenommenen Stäbchen noch der -hingelegten Stäbchen die gegebene maximale Anzahl der Umlegungen, wird die -Ziffer entsprechend geändert. Ansonsten wird probiert die Ziffer zu der nächst -kleineren Ziffern zu ändern. Dies wird so lange wiederholt bis die Änderung der -Ziffer erfolgreich war oder bereits alle Ziffern, die größer sind, ausprobiert -wurden. Danach wird zu der nächsten Ziffer übergegangen. +Algorithmus geht, angefangen mit der ersten Ziffer, jede Ziffer der Zahl durch. +Davon ausgenommen sind Ziffern, die schon geändert wurden. Bei jeder Ziffer, +sofern sie nicht schon F ist, wird probiert sie auf F,E,... umzulegen. Dabei +wird gezählt, +wie viele Stäbchen weggenommenen und hingelegt werden müssen. Überschreitet +weder die Gesamtzahl der weggenommenen Stäbchen, noch die der hingelegten Stäbchen +die gegebene maximale Anzahl der Umlegungen, wird die Ziffer entsprechend +geändert. Ansonsten wird probiert die Ziffer zu der nächst kleineren Ziffern zu +ändern. Dies wird so lange wiederholt, bis die Änderung der Ziffer erfolgreich +war oder bereits alle Ziffern, die größer sind, ausprobiert wurden. Danach wird +zu der nächsten Ziffer übergegangen. Nachdem die vorderen Ziffern entsprechend erhöht wurden, kann es sein, dass noch Stäbchen benötigt werden oder loszuwerden sind, damit gleich viele Stäbchen weggenommenen wie hingelegt wurden. Da zuvor schon die Ziffern nach Möglichkeit erhöht wurden, kann dies nur erreicht werden, indem Ziffern verringert werden. Damit das nicht so stark ins Gewicht fällt, wird über die Ziffern von hinten -iteriert. Ziffer für Ziffer wird nun probiert die Ziffer so zu ändern, dass sich der Betrag der Differenz von der -Anzahl der hingelegten und der weggenommenen Stäbchen zu verringert. Es kann -auch eine vorherige Änderung der Ziffer noch geändert werden. Dabei darf -natürlich nicht die maximale Anzahl der Umlegungen überschritten werden. +iteriert. Ziffer für Ziffer wird nun probiert, die Ziffer so zu ändern, dass sich +der Abstand zwischen der Anzahl der hingelegten und der weggenommenen Stäbchen +verringert. Es kann auch eine vorherige Änderung der Ziffer wieder rückgängig +gemacht +werden. Es kommt zu einem Fehler, wenn man bei einer hinteren Ziffer weniger +Stäbchen hätte loswerden bzw. wegnehmen sollen, um später bei einer Ziffer, die +weiter vorne ist, mehr Stäbchen hinlegen bzw. wegnehmen zu können. Wenn sie +dadurch größer würde oder es dadurch überhaupt erst möglich wäre den +Ausgleichsvorgang schon bei ihr abzuschließen, ist das Ergebnis falsch. +Glücklicherweise tritt dieser Fall bei den gegebenen Beispielen nicht ein. Unter +\ref{negativ} findet sich noch ein entsprechendes Negativbeispiel. Auch bei +diesem Algorithmusteil darf natürlich nicht die maximale Anzahl der Umlegungen +überschritten werden. Der Algorithmus wird so lange wiederholt bis die maximale +Anzahl der Umlegungen ausgeschöpft wurde. -Der Algorithmus wird so lange wiederholt bis die maximale Anzahl der Umlegungen -ausgeschöpft wurde. Dazu einmal das Beispiel aus der Aufgabenstellung: Die Zahl -D24 wird erst mit 3 weggenommenen und 2 hingelegten Stäbchen auf F24 erhöht. -Danach kann man allerdings das eine Stäbchen nicht bei den Ziffern 4 oder 2 -loswerden, sodass die Zahl auf E24 geändert wird, da dort gleich viele Stäbchen -hingelegt wie weggenommenen wurden. Darauffolgend wird wieder probiert die Ziffern zu -erhöhen, wobei die erste Ziffer ausgelassen wird, sodass der Algorithmus -schließlich zur Lösung EE4 kommt. +Schließlich werden Paare von jeweils einem hingelegten und einem weggenommenen +Stäbchen gebildet, die eine Umlegung ergeben. Theoretisch könnte es passieren, +dass dabei \enquote{die Darstellung einer Ziffer komplett \enquote{geleert}} +wird. Praktisch tritt dieser Fall bei den gegebenen Beispielen jedoch nicht ein. + +Sei $n$ die Anzahl der Ziffern, so hat der Algorithmus im besten Fall eine +Laufzeit von $O(n) = n$, wenn keine Wiederholung notwendig ist. Dies ist für die +Beispiele 2-5 auch die tatsächliche Laufzeit. Da es maximal $n$ Wiederholung +geben kann, ist die Laufzeit im schlechtesten Fall $O(n) = n^2$ -8D5 F89 \section{Umsetzung} +\subsection{Umgebung und Bibliotheken} +Die Lösungsidee wird in Rust implementiert. Zum Erleichtern der Implementierung +habe ich noch zwei crates\footnote{Abhängigkeiten in Rust} benutzt: +\begin{enumerate} + \item Mit clap werden die Kommandozeilenargumente verarbeitet. + \item derive\_more bietet u. a. die Möglichkeit die einzelnen Attribute + einer Struktur jeweils zu addieren oder zu subtrahieren wie bei + einem Vektor. +\end{enumerate} + +\subsection{Benutzung} +Dem Programm muss als Argument der Dateipfad zu der Eingabedatei übergeben +werden. Außerdem können noch weitere Optionen angepasst werden. Mit der Option +-d kann die Anzahl der Ziffern, die beim Ausgeben des Zwischenstands der +Umlegungen maximal in einer Zeile anzeigt werden sollen, geändert werden. +Standardmäßig sind dies 20 Ziffern, da jede Ziffer ein +ASCII-Art\footnote{ASCII-Art ist die Darstellung eines Bildes oder Piktogramms +mit Zeichen der ASCII-Codierung.} ist und +dementsprechend Platz braucht. Beim Verwenden des flags -n wird die Anzahl +der durchgeführten Umlegungen ausgegeben. Benutzt man den flag -s, so wird der +Zwischenstand der 7-Segmentanzeige nach jeder Umlegung ausgegeben. Schließlich +kann man den flag -l verwenden, für den Latex-Code mit dem Ergebnis des +Programms. Dies ist hilfreich, da dafür gesorgt wird, dass kein Seitenumbruch +mitten in einem ASCII-Art auftritt. + +\subsection{Implementierungsart} +Die Implementierung ist in zwei Dateien aufgeteilt. In der Datei lib.rs wird die +größtmögliche Hex-Zahl ermittelt und die Umlegungen werden entsprechend +generiert. In der Datei main.rs werden die Nutzereingaben verarbeitet und das +Ergebnis sowie weitere Informationen ausgegeben. + +Kommen wir zum Code in der Datei main.rs: Zuerst werden in der +main-Funktion die Kommandozeilenargumente verarbeitet. Danach wird die Funktion +\mintinline{rs}|hexmax::largest_hex| aufgerufen. Diese ermittelt die +größtmögliche Hex-Zahl mit der gegebenenen maximalen Anzahl der Umlegungen. +Danach wird die Hex-Zahl mit der Funktion \mintinline{rs}|hexmax::to_hex_str| zu +einem String konvertiert und ausgegeben. Falls die Zwischenstände der Umlegungen +ausgegeben werden sollen, wird die Funktion \mintinline{rs}|hexmax::gen_swaps| +aufgerufen, die die Umlegungen zurückgibt. Anschließend wird jede Umlegung +hintereinander ausgeführt und die 7-Segmentanzeige nach jeder Umlegung mithilfe +der Funktion \mintinline{rs}|swap_print| als ASCII-Art ausgegeben. + +In der Datei lib.rs gibt ein konstantes Array namens \mintinline{rs}|HEX|, dass +am Index der jeweiligen Ziffer eine Bitmap der 7-Segmentrepräsentation enthält. +Bei 1 leuchtet das Segment und bei 0 nicht. + +Die Funktion \mintinline{rs}|change_digits| durchläuft Ziffer für Ziffer eines +als Argument übergebenen Iterators. Für jede Ziffer wird nun jede mögliche +Änderung der Ziffer durchgegangen, angefangen mit F,E,... . Dabei wird nach +folgender Vorgehensweise berechnet, wie viele Stäbchen im Vergleich zur +ursprünglichen Ziffer hingelegt und weggenommenen werden müssen: + +Sei $u$ die 7-Segmentbitmap der ursprünglichen Ziffer und $n$ die der neuen +Ziffer: +\begin{align} + c = u \oplus n \\ + h = c \& n \label{h} \\ + g = c \& u \label{g} +\end{align} +Mit diesen Formeln erhält man eine Bitmap der hingelegten Stäbchen $h$ und der +weggenommenen Stäbchen $g$. Zählt man nun die Einsen der binären Schreibweise +von $h$ oder $g$, so ergibt sich die Anzahl der hingelegten bzw. weggenommenen +Stäbchen. Mit diesen sowie der Gesamtzahl der hingelegten bzw. weggenommenen +Stäbchen wird eine der Funktion \mintinline{rs}|change_digits| übergebene +Lambdafunktion aufgerufen, sofern bei der Ziffernänderung nicht die maximale Anzahl der +Umlegungen überschritten werden würde. Diese entscheidet mit diesen Angaben, ob die Ziffer +entsprechend geändert werden soll. Diese Änderung ist entweder final oder es +wird probiert, die Ziffer auf einen niedrigen Wert zu ändern. + +In der Funktion \mintinline{rs}|largest_hex| wird zuerst die Eingabedatei +ausgelesen. Im Vector \mintinline{rs}|digits| werden die einzelnen Ziffern der +gegebenen Hex-Zahl gespeichert. Innerhalb einer for-Schleife wird der +Algorithmus wiederholt, bis die Anzahl der maximalen Umlegungen ausgeschöpft ist +oder er schon so oft wiederholt wurde wie die Anzahl der Ziffern. Letzteres +bedeutet nämlich, dass keine größere Zahl mehr gefunden werden kann, die alle +Umlegungen ausschöpft. Zuerst wird probiert die vorderen Ziffern zu erhöhen. +Dafür wird die Funktion \mintinline{rs}|change_digits| mit einer Lambdafunktion +aufgerufen, die gleich die erste Ziffernänderung final akzeptiert. Die erste ist +dabei automatisch die größtmögliche Erhöhung. + +Wenn die Anzahl der hingelegten ungleich der weggenommenen Stäbchen ist, müssen +noch Stäbchen genommen bzw. hingelegt werden. Dafür wird Ziffer für Ziffer von +hinten mithilfe der Funktion \mintinline{rs}|change_digits| betrachtet. Jede +Ziffer wird nun so geändert, dass der Abstand zwischen den hingelegten und +weggenommenen Stäbchen am geringsten ist. Nachdem alle Ziffern durchgegangen +worden sind, ist die Anzahl der hingelegten gleich der weggenommenen Stäbchen. +Allerdings kann es sein, dass dafür eine vorherige Änderung rückgängig gemacht +werden musste und die maximale Anzahl der Umlegungen nicht ausgeschöpft wurde. +In diesem Fall wird der Algorithmus nochmal wiederholt. + +Die Funktion \mintinline{rs}|gen_swaps| generiert aus den Änderungen der Ziffern +konkrete Umlegungen. Dafür wird in einer for-Schleife über jede Ziffernänderung +iteriert. Wie in \eqref{h} und \eqref{g} beschrieben, wird ermittelt welche +Stäbchen dafür hingelegt und weggenommenen werden müssen. Die Bitmaps $h$ und +$g$ werden Bit für Bit durchlaufen. Für jede 1, d. h. das Stäbchen wurde +geändert, wird diese Änderung dem Vector \mintinline{rs}|moves_put| angehängt, +wenn das Stäbchen hingelegt wurde. Wenn es weggenommenen wurde, dann wird sie +dem Vector \mintinline{rs}|moves_taken| angehängt. Die beiden +Vectors\footnote{Da Vector der Name der Struktur ist, habe ich hier auch die +englische Mehrzahl verwendet. Eigentlich müsste es heißen \enquote{Variablen der +Struktur Vector}} werden schließlich zurückgegeben. Ordnet man die Elemente der +Vectors mit dem gleichen Index einander zu, so erhält man die Umlegungen. + +Die Funktion \mintinline{rs}|to_hex_str| konvertiert eine Slice mit den Ziffern +zu einem hexadezimalen String. Dies wird erreicht, indem mit einer for-Schleife +über alle Ziffern iteriert wird, und jede Ziffer einzeln zu dem entsprechenden +Zeichen konvertiert wird. \section{Beispiele} +\subsection{gegebenene Beispiele} +\beispiel{0} +Die Zahl D24 wird erst mit 3 weggenommenen und 2 hingelegten Stäbchen auf F24 +erhöht. Danach kann man allerdings das übrige Stäbchen nicht bei den Ziffern 4 +oder 2 loswerden, sodass die Zahl auf E24 geändert wird, da dort gleich viele +Stäbchen hingelegt wie weggenommenen wurden. Darauffolgend wird wieder probiert +die Ziffern zu erhöhen, wobei die erste Ziffer ausgelassen wird, sodass der +Algorithmus schließlich zur Lösung EE4 kommt. + +\beispiel{1} +\beispiel{2} +\newcounter{beispielcounter} +\forloop{beispielcounter}{3}{\value{beispielcounter}<6}{ + hexmax\arabic{beispielcounter}.txt: + \inputminted[breaklines,breakanywhere]{text}{ergebnisdateien/\arabic{beispielcounter}.txt} +} + +\subsection{Negativbeispiel\label{negativ}} +\begin{minted}[]{text} +$ hexmax testdaten/negativBeispiel.txt +ED8 +\end{minted} + +Die richtige Lösung wäre F89. \section{Quellcode} -Unwichtige Teile des Programms sollen hier nicht abgedruckt werden. Dieser Teil sollte nicht mehr als 2–3 Seiten umfassen, maximal 10. +src/lib.rs: +\inputminted[breaklines,fontsize=\footnotesize]{rs}{src/lib.rs} \end{document} diff --git a/Aufgabe3-HexMax/ergebnisdateien/0.txt b/Aufgabe3-HexMax/ergebnisdateien/0.txt new file mode 100644 index 0000000..cb6c301 --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/0.txt @@ -0,0 +1 @@ +größte Hex-Zahl: EE4 diff --git a/Aufgabe3-HexMax/ergebnisdateien/1.txt b/Aufgabe3-HexMax/ergebnisdateien/1.txt new file mode 100644 index 0000000..6008a16 --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/1.txt @@ -0,0 +1 @@ +größte Hex-Zahl: FFFEA97B55 diff --git a/Aufgabe3-HexMax/ergebnisdateien/2.txt b/Aufgabe3-HexMax/ergebnisdateien/2.txt new file mode 100644 index 0000000..20de5c8 --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/2.txt @@ -0,0 +1 @@ +größte Hex-Zahl: FFFFFFFFFFFFFFFFD9A9BEAEE8EDA8BDA989D9F8 diff --git a/Aufgabe3-HexMax/ergebnisdateien/3.txt b/Aufgabe3-HexMax/ergebnisdateien/3.txt new file mode 100644 index 0000000..9d49cbd --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/3.txt @@ -0,0 +1 @@ +größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA98BB8B9DFAFEAE888DD888AD8BA8EA8888 diff --git a/Aufgabe3-HexMax/ergebnisdateien/4.txt b/Aufgabe3-HexMax/ergebnisdateien/4.txt new file mode 100644 index 0000000..e5fca59 --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/4.txt @@ -0,0 +1 @@ +größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB8DE88BAA8ADD888898E9BA88AD98988F898AB7AF7BDA8A61BA7D4AD8F888 diff --git a/Aufgabe3-HexMax/ergebnisdateien/5.txt b/Aufgabe3-HexMax/ergebnisdateien/5.txt new file mode 100644 index 0000000..5483bc3 --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/5.txt @@ -0,0 +1 @@ +größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88EFA9EBE89EFA99FBDAA8E8EAD88AB899F8E8F9AA9E9AD88988EDA9A99888EDAD989A8BAFD8A888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 diff --git a/Aufgabe3-HexMax/ergebnisdateien/negativBeispiel.txt b/Aufgabe3-HexMax/ergebnisdateien/negativBeispiel.txt new file mode 100644 index 0000000..e59a8ea --- /dev/null +++ b/Aufgabe3-HexMax/ergebnisdateien/negativBeispiel.txt @@ -0,0 +1 @@ +größte Hex-Zahl: ED8 diff --git a/Aufgabe3-HexMax/src/lib.rs b/Aufgabe3-HexMax/src/lib.rs index fa25922..9c55476 100644 --- a/Aufgabe3-HexMax/src/lib.rs +++ b/Aufgabe3-HexMax/src/lib.rs @@ -1,32 +1,51 @@ use std::fs; +// Struktur für eine veränderte Ziffer #[derive(Clone, Copy)] struct Move { digit: u8, swaps: PutTaken, } +// enthält den ursprünglichen Wert der Ziffer und den aktuellen pub struct DigitHistory { pub original_digit: u8, move_option: Option, } +// zur Angabe welche Stäbchen verändert wurden pub struct ChangedSticks { pub digit_i: usize, pub stick_map: u8, } +/* vektorartige Struktur, die die Anzahl der hingelegten und weggenommenen Stäbchen enthält. + * Rechenoperationen können, wie bei Vektoren durchgeführt werden. */ #[derive(Clone, Copy, derive_more::SubAssign, derive_more::AddAssign, derive_more::Add)] struct PutTaken { put: u16, taken: u16, } +/* Rückgabetyp der Lambdafunktion, die ein Argument von change_digits ist. Damit kann das weitere + * Vorgehen der Funktion gesteuert werden. */ struct ChangeDigitsControl { change_digit: bool, + + /* Bei true wird aus der inneren for-Schleife von change_digits ausgebrochen, sodass Änderungen + * auf kleinere Ziffer nicht mehr ausprobiert werden. */ break_free: bool, } +/* Bitmaps der 7-Segmentrepräsentation der Hex-Zahlen. Bei 1 ist ein Stäbchen da und bei 0 nicht. Es + * folgt eine Auflistung, welches Bit für welches Stäbchen steht. + * 2. höchstwertiges Bit: Stäbchen oben in der Mitte + * 3. höchstwertiges Bit: Stäbchen oben links + * 4. höchstwertiges Bit: Stäbchen oben rechts + * 5. höchstwertiges Bit: zentrales Stäbchen + * 6. höchstwertiges Bit: Stäbchen unten links + * 7. höchstwertiges Bit: Stäbchen unten rechts + * 8. höchstwertiges Bit: Stäbchen unten in der Mitte */ pub const HEX: [u8; 16] = [ 0b1110111, // 0 0b0010010, // 1 @@ -46,32 +65,57 @@ pub const HEX: [u8; 16] = [ 0b1101100, // F ]; +// findet die größte Hex-Zahl und gibt diese mit der Anzahl der Umlegungen zurück. pub fn largest_hex(filepath: &str) -> (Vec, u16) { - let mut digits: Vec; + let mut digits: Vec; // enthält die Ziffern und wie sie umgelegt wurden let max_swaps: u16; { - let content = fs::read_to_string(filepath).unwrap(); + let content = fs::read_to_string(filepath).unwrap(); // liest die Eingabedatei in einen String let mut lines = content.lines(); - let line = lines.next().unwrap(); - digits = Vec::with_capacity(line.len()); - for character in line.chars() { + let first_line = lines.next().unwrap(); + digits = Vec::with_capacity(first_line.len()); // die Länge der ersten Zeile ist die Anzahl der Ziffern + for character in first_line.chars() { // für jedes Zeichen der ersten Zeile digits.push(DigitHistory { + + // konvertiert die Ziffer in binäre Repräsentation original_digit: character.to_digit(16).unwrap() as u8, move_option: None, }); } - max_swaps = lines.next().unwrap().parse().unwrap(); + max_swaps = lines.next().unwrap().parse().unwrap(); // liest die Anzahl der maximalen Umlegungen aus } { + // gibt an wie viele Stäbchen insgesamt hingelegt und weggenommenen wurden let mut total_swaps = PutTaken{ put: 0, taken: 0}; - for i in 0..digits.len() { + for _ in 0..digits.len() { // wiederholt den Algorithmus + + /* Wenn die maximalen Umlegungen schon ausgereizt sind, wurde die größte Hex-Zahl + * gefunden. */ if total_swaps.taken == total_swaps.put && total_swaps.put == max_swaps {break;} + + /* beginnt von vorne an jede Ziffer durchzugehen und ändert jede auf die größtmögliche + * Ziffer */ total_swaps = change_digits(digits.iter_mut(), total_swaps, max_swaps, (), true, |_, _, _| { ChangeDigitsControl {change_digit: true, break_free: true} }); + + /* Wenn die Anzahl der hingelegten und weggenommenen Stäbchen ungleich ist, wird von + * hinten an probiert dies auszugleichen. */ if total_swaps.put != total_swaps.taken { - total_swaps = change_digits(digits.iter_mut().rev(), total_swaps, max_swaps, i16::MAX, false, |new_total_swaps, total_swaps, best_diff| { + + /* new_total_swaps ist die Anzahl der hingelegten und weggenommenen Stäbchen, wenn + * man die Ziffer entsprechend ändert. + * total_swaps ist die aktuelle Anzahl der hingelegten und weggenommenen Stäbchen. + * best_diff ist der geringste Abstand zwischen der Anzahl der hingelegten und weggenommenen + * Stäbchen. */ + total_swaps = change_digits(digits.iter_mut().rev(), total_swaps, max_swaps, i16::MAX, false, + |new_total_swaps, total_swaps, best_diff| { + + // Abstand, wenn man die Ziffer ändern würde let new_diff = (new_total_swaps.put as i16 - new_total_swaps.taken as i16).abs(); + + /* Wenn der neue Abstand kleiner ist als die alte, sollte man die Ziffer + * ändern. */ let change_digit = new_diff < (total_swaps.put as i16 - total_swaps.taken as i16).abs() && new_diff < *best_diff; if change_digit {*best_diff = new_diff;} @@ -79,11 +123,11 @@ pub fn largest_hex(filepath: &str) -> (Vec, u16) { }); } } - println!("taken {}, put {}", total_swaps.taken, total_swaps.put); (digits, max_swaps) } } +// konvertiert eine Slice von Ziffern zu einem hexadezimalen String pub fn to_hex_str(digits: &[DigitHistory]) -> String { let mut largest_hex_str = String::with_capacity(digits.len()); for digit in digits { @@ -92,19 +136,34 @@ pub fn to_hex_str(digits: &[DigitHistory]) -> String { largest_hex_str } +// generiert die konkreten Umlegungen pub fn gen_swaps(digits: &[DigitHistory], number_of_swaps: usize) -> (Vec, Vec) { + + // Stäbchen, die hingelegt wurden let mut moves_put: Vec = Vec::with_capacity(number_of_swaps); + + // Stäbchen, die weggenommenen wurden let mut moves_taken: Vec = Vec::with_capacity(number_of_swaps); + + // für jede Ziffer for (j, DigitHistory {original_digit, move_option}) in digits.iter().enumerate() { - if let Some(moved) = move_option { - let from_hex = HEX[*original_digit as usize]; + if let Some(moved) = move_option { // Wenn die Ziffer geändert wurde + let from_hex = HEX[*original_digit as usize]; // 7-Segmentrepräsentation der urspünglichen Ziffer + + // 7-Segmentrepräsentation der Ziffer auf die sie geändert wurde let to_hex = HEX[moved.digit as usize]; let changed = from_hex ^ to_hex; - let put = changed & to_hex; - let taken = changed & from_hex; + let put = changed & to_hex; // Bitmap der hingelegten Stäbchen + let taken = changed & from_hex; // Bitmap der weggenommenen Stäbchen + + // für die hingelegten und weggenommenen Stäbchen for (put_or_taken, moves_put_or_taken) in [(put, &mut moves_put), (taken, &mut moves_taken)] { + + // für jedes Stäbchen der Ziffer for i in 0..u8::BITS { let stick_map = put_or_taken & 1 << i; + + // Wenn das Stäbchen geändert wurde, wird die Änderung in einem Vector gespeichert. if stick_map != 0 { moves_put_or_taken.push(ChangedSticks { digit_i: j, @@ -118,31 +177,55 @@ pub fn gen_swaps(digits: &[DigitHistory], number_of_swaps: usize) -> (Vec, T: Copy, F: Fn(PutTaken, PutTaken, &mut T) -> ChangeDigitsControl> (iter: I, mut total_swaps: PutTaken, max_swaps: u16, init_t: T, higher_digit: bool, run: F) -> PutTaken { for DigitHistory {original_digit, move_option} in iter { + + // bereits geänderte Ziffern werden nicht mehr erhöht if higher_digit && move_option.is_some() {continue;} + + /* Anzahl der hingelegten und weggenommenen Stäbchen, wenn man die mögliche Änderung + * der Ziffer rückgängig machen würde. */ let mut swaps_with_undo = total_swaps; if let Some(moved) = move_option { swaps_with_undo -= moved.swaps; } - let digit_hex = HEX[*original_digit as usize]; + let digit_hex = HEX[*original_digit as usize]; // 7-Segmentrepräsentation der urspünglichen Ziffer let mut best_move: Option = None; let mut t = init_t; + + /* für jede Hex-Ziffer auf die man die Ziffer ändern könnte. Dabei wird bei der größten + * Hex-Ziffer begonnen. */ for (j, to_digit) in HEX.iter().enumerate().rev() { + + /* Auf die gleich Ziffer zu ändern, ergibt keinen Sinn. Wenn nur auf höhere Ziffern + * geändert werden soll, wird die Änderung abgebrochen, ansonsten mit der nächsten + * Ziffer fortgefahren. */ if j as u8 == *original_digit { if higher_digit {break;} else {continue;} } let changed_sticks = digit_hex ^ *to_digit; + + // Anzahl der neu hingelegten und weggenommenen Stäbchen let swaps = PutTaken { put: (changed_sticks & *to_digit).count_ones() as u16, taken: (changed_sticks & digit_hex).count_ones() as u16, }; + // neue Gesamtzahl der hingelegten und weggenommenen Stäbchen let new_total_swaps = swaps_with_undo + swaps; + + // Wenn die maximale Anzahl der Umlegungen nicht überschritten wurde if new_total_swaps.put <= max_swaps && new_total_swaps.taken <= max_swaps { let control = run(new_total_swaps, total_swaps, &mut t); + + // Wenn die Ziffer geändert werden soll, wird dies als beste Änderung gespeichert. if control.change_digit { best_move = Some(Move { digit: j as u8, @@ -152,6 +235,7 @@ fn change_digits<'a, I: Iterator, T: Copy, if control.break_free {break;} } } + // führt die Änderung der Ziffer durch if let Some(moved) = best_move { *move_option = best_move; total_swaps = swaps_with_undo + moved.swaps; diff --git a/Aufgabe3-HexMax/src/main.rs b/Aufgabe3-HexMax/src/main.rs index d6b2c5c..80d45bb 100644 --- a/Aufgabe3-HexMax/src/main.rs +++ b/Aufgabe3-HexMax/src/main.rs @@ -13,40 +13,71 @@ struct Args { /// Ausgabe der Anzahl der Umlegungen #[clap(short, long)] number_of_swaps_print: bool, + + /// maximale Anzahl der Ziffern der 7-Segmentanzeige in einer Zeile + #[clap(short, long, default_value_t = 20)] + digits_per_line: u16, + + /// gibt Latex-Code aus, um das Ergebnis des Programms einzubetten + #[clap(short, long)] + latex: bool, } fn main() { - let args = Args::parse(); + let args = Args::parse(); // liest die Argumente ein + if args.latex {minted_begin();} // Anfang des einzubettenden Texts + + // bildet die größte Hex-Zahl mithilfe der Eingabedatei let (digits, number_of_swaps) = hexmax::largest_hex(&args.filepath); - println!("{}", hexmax::to_hex_str(&digits)); + println!("größte Hex-Zahl: {}", hexmax::to_hex_str(&digits)); // und gibt sie aus if args.number_of_swaps_print {println!("Anzahl der Umlegungen: {}", number_of_swaps);} if args.swap_print { + + // generiert die Umlegungen let (moves_put, moves_taken) = hexmax::gen_swaps(&digits, number_of_swaps as usize); + + // konvertiert die Ziffern zu der 7-Segmentrepräsentation let mut digits_hex: Vec = digits.iter() .map(|digit| hexmax::HEX[digit.original_digit as usize]).collect(); + println!("Umlegungen:"); - swap_print(&digits_hex); - println!(); - let mut not_first = false; - for (move_put, move_taken) in moves_put.iter().zip(&moves_taken) { - if not_first {println!();} + swap_print(&digits_hex, args.digits_per_line); // gibt Startzahl als 7-Segmentrepräsentation aus + for (i, (move_put, move_taken)) in moves_put.iter().zip(&moves_taken).enumerate() { + println!(); + + // bettet jedes ASCII-Art extra ein, damit kein Seitenumbruch in einem ASCII-Art ist + if args.latex { + minted_end(); + minted_begin(); + } + println!("Nach {}. Umlegung", i + 1); + + // ändere die Zahl der Umlegung entsprechend for hexmax::ChangedSticks {digit_i, stick_map} in [move_put, move_taken] { digits_hex[*digit_i] ^= stick_map; + if digits_hex[*digit_i] == 0 { + panic!("Bei den generierten Umlegungen wurde eine Ziffer komplett geleert."); + } } - swap_print(&digits_hex); - not_first = true; + // gibt den neuen Zwischenstand aus + swap_print(&digits_hex, args.digits_per_line); } } + if args.latex {minted_end();} // Ende des einzubettenden Texts +} + +// gibt die 7-Segmentanzeige aus +fn swap_print(digits_hex: &[u8], digits_per_line: u16) { + for digits_line in digits_hex.chunks(digits_per_line as usize) { + print_vertical(digits_line, 6); + print_horizontal(digits_line, 5); + print_vertical(digits_line, 3); + print_horizontal(digits_line, 2); + print_vertical(digits_line, 0); + } } -fn swap_print(digits_hex: &[u8]) { - print_vertical(digits_hex, 6); - print_horizontal(digits_hex, 5); - print_vertical(digits_hex, 3); - print_horizontal(digits_hex, 2); - print_vertical(digits_hex, 0); -} - +// Hilfsfunktion für die Ausgabe der Zahlen. Sie ruft für jede Zahl die Lambdafunktion auf. fn print_helper(digits_hex: &[u8], run: F) { let mut not_first = false; for digit in digits_hex { @@ -57,16 +88,28 @@ fn print_helper(digits_hex: &[u8], run: F) { println!(); } +/* Wenn an der entsprechenden Position ein Stäbchen ist, wird das Zeichen, das das Stäbchen + * repräsentiert zurückgegeben, ansonsten ein Leerzeichen. */ fn print_stick(digit: u8, i: u8, stick_char: char) -> char { if digit >> i & 1 == 1 {stick_char} else {' '} } +// Funktion, um zwei horizontale, nebeneinander liegende Stäbchen auszugeben fn print_horizontal(digits_hex: &[u8], i: u8) { print_helper(digits_hex, |digit| print!("{} {}", print_stick(digit, i, '|'), print_stick(digit, i - 1, '|')) ); } +// Funktion, die ein vertikal liegendes Stäbchen ausgibt fn print_vertical(digits_hex: &[u8], i: u8) { print_helper(digits_hex, |digit| print!(" {} ", print_stick(digit, i, '_'))); } + +fn minted_begin() { + println!("{}", r"\begin{minted}[samepage]{text}"); +} + +fn minted_end() { + println!("{}", r"\end{minted}"); +} diff --git a/Aufgabe3-HexMax/testdaten/0.txt b/Aufgabe3-HexMax/testdaten/negativBeispiel.txt similarity index 100% rename from Aufgabe3-HexMax/testdaten/0.txt rename to Aufgabe3-HexMax/testdaten/negativBeispiel.txt