final commit
This commit is contained in:
@ -63,82 +63,97 @@
|
|||||||
\section{Lösungsidee}
|
\section{Lösungsidee}
|
||||||
\subsection{Allgemeines}
|
\subsection{Allgemeines}
|
||||||
Der Beweis, ob ein Rechenrätsel eindeutig lösbar ist, ist ein
|
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
|
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
|
auszuprobieren. Kommt ein Ergebnis nur einmal vor, wurde ein eindeutig lösbares
|
||||||
gefunden, was in der Regel der Fall sein sollte, ansonsten muss es mit anderen
|
Rätsel gefunden, was in der Regel der Fall sein sollte, ansonsten muss der
|
||||||
Ziffern wiederholt werden. Theoretisch wäre es möglich, dass immer die Operanden
|
Vorgang mit anderen Ziffern wiederholt werden.
|
||||||
so zufällig so gewählt werden, dass es kein interessantes und eindeutiges Rätsel
|
|
||||||
|
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
|
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
|
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$
|
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
|
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
|
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
|
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
|
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.
|
viele Möglichkeiten können noch mit einer guten Laufzeit berechnet werden.
|
||||||
|
|
||||||
Eindeutigkeit auch mit ganzen Zwischenergebnissen?
|
|
||||||
|
|
||||||
\subsection{Die Null als Operand?}
|
\subsection{Die Null als Operand?}
|
||||||
|
|
||||||
Nach Aufgabenstellung ist jeder Operand nur eine Ziffer. Bei den Beispielen
|
Nach Aufgabenstellung ist jeder Operand nur eine Ziffer. Bei den gegebenen
|
||||||
kommt jede Ziffer vor außer die Null, allerdings wird auch nicht ausdrücklich
|
Beispielen kommt jede Ziffer vor außer der Null, allerdings wird auch nicht
|
||||||
gesagt, dass man sie nicht verwenden darf. Die Addition und Subtraktion einer
|
ausdrücklich gesagt, dass man sie nicht verwenden darf. Die Addition und
|
||||||
Null kann in einem Rätsel nicht verwendet werden, da beide Operationen das
|
Subtraktion einer Null kann in einem Rätsel nicht verwendet werden, da beide
|
||||||
gleiche Ergebnis haben und das Rätsel damit uneindeutig wäre. Da durch null
|
Operationen das gleiche Ergebnis haben und das Rätsel damit uneindeutig wäre. Da
|
||||||
Dividieren nicht definiert ist, bleibt nur noch die Multiplikation übrig. Weil
|
die Division durch Null nicht definiert ist, bleibt nur noch die Multiplikation
|
||||||
mit null Multiplizieren immer null ergibt, gilt auch für die Operatoren links
|
übrig. Es ergibt jedoch wenig Sinn ein Rätsel zu stellen, wo bereits zu Beginn
|
||||||
des Zwischenergebnisses, dass sie eine Multiplikation sein müssen. Dazu ein
|
ein Teil der Lösung bekannt ist. Dies gilt insbesondere, da die Rätsel nach
|
||||||
Beispiel: Da bei $1\circ4\circ0$ das letzte \enquote{$\circ$} durch eine
|
Aufgabenstellung \enquote{richtig schwer} sein sollen. Also wird die Null in
|
||||||
Multiplikation ersetzt werden muss, also $1\circ4\cdot0=1\circ0$, muss auch das
|
meiner Lösung nicht als Operand benutzt.
|
||||||
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.
|
|
||||||
|
|
||||||
\subsection{interessante Rätsel}
|
\subsection{Interessante Rätsel}
|
||||||
Die Aufgabenstellung gibt nicht genau vor wie ein Rätsel, das \enquote{interessant und
|
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
|
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}
|
\begin{minted}{text}
|
||||||
Rätsel: 4 ○ 3 ○ 2 ○ 6 ○ 3 ○ 9 ○ 7 ○ 8 ○ 2 ○ 9 ○ 4 ○ 4 ○ 6 ○ 4 ○ 4 ○ 5 = 4792
|
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
|
Lösung: 4 * 3 * 2 * 6 * 3 * 9 + 7 * 8 : 2 * 9 * 4 - 4 * 6 - 4 * 4 * 5 = 4792
|
||||||
\end{minted}
|
\end{minted}
|
||||||
Es fällt auf das jeder Operator mindestens einmal vorkommt, wobei Multiplikation
|
Es fällt auf das jeder Operator mindestens einmal vorkommt, wobei die
|
||||||
überwiegt. Außerdem ist das Ergebnis noch relativ klein im Vergleich zu anderen
|
Multiplikation überwiegt. Außerdem ist das Ergebnis noch relativ klein im
|
||||||
eindeutig lösbaren Rätseln. Zudem kommen einige verschiedene Ziffern vor.
|
Vergleich zu anderen eindeutig lösbaren Rätseln, wie ich beim Vergleich mit der
|
||||||
Meine Regeln für ein interessantes Rätsel sind noch etwas strenger, da hier
|
ungefilterten Ausgabe meines Programms festgestellt habe. Zudem kommen einige
|
||||||
schon recht oft Multiplikation vertreten ist. Diese lauten wie folgt:
|
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}
|
\begin{enumerate}
|
||||||
\item Bei $n$ Operatoren muss für $m_i$, die Anzahl, mit der jeder der
|
\item Sei $n$ die Anzahl der Operatoren und $m_i$, die Anzahl, mit der
|
||||||
vier Operatoren vorkommt, gelten:
|
der jeweilige Operator vorkommt. Dann soll gelten:
|
||||||
\begin{align}
|
\begin{align}
|
||||||
\frac{n}{10} - 1 < m_i \leq \frac{n}{2} + 1
|
\frac{n}{10} - 1 < m_i \leq \frac{n}{2} + 1
|
||||||
\end{align}
|
\end{align}
|
||||||
\item Bei $n$ Ziffern muss für $m_i$, die Anzahl, mit der jede der neun
|
Die Division ist oft nicht möglich, da das Ergebnis keine ganze
|
||||||
Ziffern vorkommt, gelten:
|
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}
|
\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}
|
\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}
|
\end{enumerate}
|
||||||
In der Regel werden viele interessante Rätsel gefunden. Da das Beispielrätsel
|
In der Regel werden viele interessante Rätsel gefunden. Da das Beispielrätsel
|
||||||
ein kleines Ergebnis hat und kleinere Ergebnisse auch eleganter sind, wird
|
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}
|
\section{Umsetzung}
|
||||||
\subsection{Umgebung und Bibliotheken}
|
\subsection{Umgebung und Bibliotheken}
|
||||||
Die Lösungsidee wird in Rust nightly implementiert, da Trait Aliase noch nicht
|
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
|
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
|
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}
|
\begin{itemize}
|
||||||
\item Mit rand werden zufällige Ziffern als Operanden erstellt.
|
\item Mit rand werden zufällige Ziffern als Operanden erstellt.
|
||||||
\item Mit clap werden die Kommandozeilenargumente verarbeitet.
|
\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
|
Dabei wird nicht das meta-crate num verwendet, sondern die
|
||||||
sub-crates werden einzeln verwendet, um die Abhängigkeiten
|
sub-crates werden einzeln verwendet, um die Abhängigkeiten
|
||||||
kleinzuhalten. num-traits ist dabei u. a. für die Konvertierung
|
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}
|
\subsection{Benutzung}
|
||||||
Mit der Option -c kann die Anzahl der Operatoren angegeben werden. Der
|
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
|
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
|
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
|
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
|
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.
|
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.
|
Operanden nicht angibt, werden automatisch interessante gewählt.
|
||||||
|
|
||||||
\subsection{Implementierungsart}
|
\subsection{Implementierungsart}
|
||||||
|
|
||||||
In der main-Funktion befindet sich direkt eine while-Schleife, die solange läuft
|
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
|
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
|
durchlaufen, weil sich für die erste ausgewählte Operandenkombination normalerweise
|
||||||
auch ein interessantes und eindeutiges Rätsel gibt. Wenn der Nutzer keine
|
auch ein interessantes und eindeutiges Rätsel ergibt.
|
||||||
|
|
||||||
|
Wenn der Nutzer keine
|
||||||
Operanden angibt, werden diese zufällig ausgewählt. Dabei befinden sich die
|
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
|
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
|
garantiert werden kann, sodass das Rätsel interessant ist, werden in Laufe der
|
||||||
Auswahl Ziffern aus dem Vector entfernt.
|
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
|
aufgerufen. Diese kann durch Generics alle Integertypen für die
|
||||||
(Zwischen)ergebnisse verwenden. Das größte Ergebnis für die Operanden wird
|
(Zwischen)ergebnisse verwenden. Das größte Ergebnis für die Operanden wird
|
||||||
berechnet, indem sie alle miteinander multipliziert werden. Wenn dieses zu groß
|
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
|
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
|
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,
|
64 Bit 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
|
die 128 Bit Integers erfordern.
|
||||||
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.
|
|
||||||
|
|
||||||
Innerhalb der Funktion ist eine for-Schleife, die über alle Möglichkeiten
|
Innerhalb der Funktion \mintinline{rs}|calc_results| gibt es eine for-Schleife,
|
||||||
Operanden mit Punkt- bzw. Strichrechnung zu wählen iteriert. Dabei wird die
|
die über alle Möglichkeiten, Operanden mit Punkt- bzw. Strichrechnung zu wählen,
|
||||||
Variable \mintinline{rs}|dm_as_map| nach jedem Durchlauf um eins erhöht. Jedes
|
iteriert. Dabei wird die Variablen \mintinline{rs}|dm_as_map| nach jedem
|
||||||
Bit der Variable steht dabei für Punkt- oder Strichrechnung, wobei das least
|
Durchlauf um eins erhöht. Jedes Bit der Variable steht dabei für Punkt- oder
|
||||||
significant Bit für den Operatoren ganz links steht.
|
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
|
In der Schleife wird die Funktion \mintinline{rs}|multiplicate_divide| mit einer Sequenz von
|
||||||
Operanden, zwischen denen nur Punktrechnung vorkommt, aufgerufen. Diese ruft
|
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.
|
Zwischenergebnis erstellt wird.
|
||||||
|
|
||||||
In der rekursiven Funktion \mintinline{rs}|add_sub| wird mit einer for-Schleife
|
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
|
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
|
Ergebnissen erreicht wurde. Dann wird das Ergebnis mit den verwendeten
|
||||||
Operatoren mithilfe der Struktur \mintinline{rs}|ResultStore| gespeichert, wobei
|
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.
|
schon eines der Zwischenergebnisse uneindeutig war.
|
||||||
|
|
||||||
Die Struktur \mintinline{rs}|ResultStore| besteht aus einer Hashmap mit den
|
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
|
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
|
sie noch ein weiters Attribut, ein Hashset, mit den Ergebnissen für die das
|
||||||
Rätsel uneindeutig wäre. Die Methode \mintinline{rs}|store| hat als Parameter
|
Rätsel uneindeutig ist. Die Methode \mintinline{rs}|store| hat als Parameter
|
||||||
das Ergebnis und eine Option von Operatoren. Wenn die Option
|
das Ergebnis und eine Option von Operatoren. Wenn die Option
|
||||||
\mintinline{rs}|None| ist, wird das Ergebnis in das Hashset der uneindeutigen
|
\mintinline{rs}|None| ist, wird das Ergebnis in das Hashset der uneindeutigen
|
||||||
Ergebnisse aufgenommen. Wenn das Ergebnis weder in der Hashmap noch in dem
|
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
|
Hashset ist, wird das Rätsel in der Hashmap gespeichert. Wenn es bereits in der
|
||||||
Hashmap ist, wird es als uneindeutig im Hashset gespeichert.
|
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
|
\mintinline{rs}|ResultStore| Struktur iteriert. Es wird gezählt wie oft welche
|
||||||
Operatoren verwendet wurden und überprüft, ob die Anzahl innerhalb des Bereiches
|
Operatoren verwendet wurden und überprüft, ob die Anzahl innerhalb des Bereiches
|
||||||
liegt, in dem das Rätsel als interessant befunden wird. Von allen interessanten
|
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
|
und uneindeutigen Rätseln wird bestimmt, welches das kleinste Ergebnis hat, und
|
||||||
diese wird ausgegeben. Wenn kein interessantes Rätsel gefunden wurde, muss die
|
dieses wird ausgegeben. Wenn kein interessantes Rätsel gefunden wurde, muss die
|
||||||
while-Schleife der main-Funktion noch einmal durchlaufen werden.
|
while-Schleife der main-Funktion noch einmal durchlaufen werden.
|
||||||
|
|
||||||
\section{Beispiele}
|
\section{Beispiele}
|
||||||
|
Im Ordner \enquote{ergebnisdateien} findet man die Beispielausgaben nummeriert
|
||||||
|
nach der Operatorenanzahl.
|
||||||
|
|
||||||
\input{|./ergebnis-latex.sh}
|
\input{|./ergebnis-latex.sh}
|
||||||
|
|
||||||
\section{Quellcode}
|
\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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PartOperation<T> {
|
||||||
|
|
||||||
|
// mögliche Zwischenergebnisse eines Teils der Rechnung, der nur Punktrechnung enthält.
|
||||||
|
results: HashMap<T, Option<u64>>,
|
||||||
|
last_operator: Option<usize>, // der Operator, der vor dieser Rechnungsteil steht
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struktur, die eindeutige Ergebnisse speichert
|
||||||
|
struct ResultStore<T> {
|
||||||
|
riddles: HashMap<T, u64>, // speichert eindeutige Rätsel
|
||||||
|
|
||||||
|
// speichert Ergebnisse, für die es bereits mehrere Operatorenkombinationen gibt
|
||||||
|
results_already_taken: HashSet<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq + Hash> ResultStore<T> {
|
||||||
|
|
||||||
|
// 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<u64>) {
|
||||||
|
|
||||||
|
// 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<u8> = 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<u8> = 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::<i64>(args.solution_print, &digits)
|
||||||
|
} else {calc_results::<i128>(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<T: BasicInteger + 'static + FromPrimitive + Display + Bounded>
|
||||||
|
(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<PartOperation<T>> = 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<T, Option<u64>> = 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<u64> = 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<Item = &'a u8>, results: &mut HashMap<T, Option<u64>>,
|
||||||
|
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<Item = &'a PartOperation<T>>,
|
||||||
|
results: &mut ResultStore<T>, part_result: T, operators: Option<u64>) {
|
||||||
|
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<usize> {
|
||||||
|
if index == 0 {None} else {Some(index as usize - 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion, um eine einzelne Ziffer als Zwischenergebnis hinzufügen
|
||||||
|
fn insert_digit<T: Eq + Hash + FromPrimitive>(digits: &[u8], i: usize) -> PartOperation<T> {
|
||||||
|
let mut result_map: HashMap<T, Option<u64>> = 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}
|
\end{document}
|
||||||
|
|||||||
2
Aufgabe2-Rechenrätsel/ergebnisdateien/23.txt
Normal file
2
Aufgabe2-Rechenrätsel/ergebnisdateien/23.txt
Normal file
@ -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
|
||||||
2
Aufgabe2-Rechenrätsel/ergebnisdateien/24.txt
Normal file
2
Aufgabe2-Rechenrätsel/ergebnisdateien/24.txt
Normal file
@ -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
|
||||||
2
Aufgabe2-Rechenrätsel/ergebnisdateien/25.txt
Normal file
2
Aufgabe2-Rechenrätsel/ergebnisdateien/25.txt
Normal file
@ -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
|
||||||
2
Aufgabe2-Rechenrätsel/ergebnisdateien/26.txt
Normal file
2
Aufgabe2-Rechenrätsel/ergebnisdateien/26.txt
Normal file
@ -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
|
||||||
@ -117,8 +117,8 @@ fn main() {
|
|||||||
* die Ziffer, die um eins größer ist. */
|
* die Ziffer, die um eins größer ist. */
|
||||||
let mut selectable_digits: Vec<u8> = Vec::from_iter(0..9);
|
let mut selectable_digits: Vec<u8> = Vec::from_iter(0..9);
|
||||||
let mut digits_count: [u8; 9] = [0; 9]; // Counter wie oft die jeweiligen Ziffern vorkommen
|
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 min: u8 = digits.len() as u8 / 18; // minmale Anzahl der jeweiligen Ziffern
|
||||||
let max: u8 = digits.len() as u8 / 4 + 1; // maximale Anzahl
|
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
|
/* Anzahl der Operanden, die verwendet werden können ohne, dass dadurch der Erfüllung
|
||||||
* der minimalen Anzahl der Ziffern nähergekommen wird. */
|
* der minimalen Anzahl der Ziffern nähergekommen wird. */
|
||||||
|
|||||||
@ -45,6 +45,13 @@
|
|||||||
%\usepackage{hyperref} % Anklickbare Links im Dokument
|
%\usepackage{hyperref} % Anklickbare Links im Dokument
|
||||||
\usepackage{cleveref}
|
\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
|
% Daten für die Titelseite
|
||||||
\title{\textbf{\Huge\Aufgabe}}
|
\title{\textbf{\Huge\Aufgabe}}
|
||||||
\author{\LARGE Teilnahme-ID: \LARGE \TeilnahmeId \\\\
|
\author{\LARGE Teilnahme-ID: \LARGE \TeilnahmeId \\\\
|
||||||
@ -60,48 +67,191 @@
|
|||||||
\vspace{0.5cm}
|
\vspace{0.5cm}
|
||||||
|
|
||||||
\section{Lösungsidee}
|
\section{Lösungsidee}
|
||||||
Durch ebenso oftes Hinlegen und Wegnehmen der Stäbchen erhält man eine Hex-Zahl,
|
Durch ebenso häufiges 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
|
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
|
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
|
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.
|
Algorithmus geht, angefangen mit der ersten Ziffer, jede Ziffer der Zahl durch.
|
||||||
Ausgenommen sind davon Ziffern, die schon geändert wurden.
|
Davon ausgenommen sind Ziffern, die schon geändert wurden. Bei jeder Ziffer,
|
||||||
Jede Ziffer wird, sofern sie nicht schon F ist, probiert auf F,E,... umzulegen.
|
sofern sie nicht schon F ist, wird probiert sie auf F,E,... umzulegen. Dabei
|
||||||
Dabei wird gezählt wie viele Stäbchen weggenommenen und hingelegt werden müssen.
|
wird gezählt,
|
||||||
Überschreitet weder die Gesamtzahl der weggenommenen Stäbchen noch der
|
wie viele Stäbchen weggenommenen und hingelegt werden müssen. Überschreitet
|
||||||
hingelegten Stäbchen die gegebene maximale Anzahl der Umlegungen, wird die
|
weder die Gesamtzahl der weggenommenen Stäbchen, noch die der hingelegten Stäbchen
|
||||||
Ziffer entsprechend geändert. Ansonsten wird probiert die Ziffer zu der nächst
|
die gegebene maximale Anzahl der Umlegungen, wird die Ziffer entsprechend
|
||||||
kleineren Ziffern zu ändern. Dies wird so lange wiederholt bis die Änderung der
|
geändert. Ansonsten wird probiert die Ziffer zu der nächst kleineren Ziffern zu
|
||||||
Ziffer erfolgreich war oder bereits alle Ziffern, die größer sind, ausprobiert
|
ändern. Dies wird so lange wiederholt, bis die Änderung der Ziffer erfolgreich
|
||||||
wurden. Danach wird zu der nächsten Ziffer übergegangen.
|
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
|
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
|
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
|
weggenommenen wie hingelegt wurden. Da zuvor schon die Ziffern nach Möglichkeit
|
||||||
erhöht wurden, kann dies nur erreicht werden, indem Ziffern verringert werden.
|
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
|
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
|
iteriert. Ziffer für Ziffer wird nun probiert, die Ziffer so zu ändern, dass sich
|
||||||
Anzahl der hingelegten und der weggenommenen Stäbchen zu verringert. Es kann
|
der Abstand zwischen der Anzahl der hingelegten und der weggenommenen Stäbchen
|
||||||
auch eine vorherige Änderung der Ziffer noch geändert werden. Dabei darf
|
verringert. Es kann auch eine vorherige Änderung der Ziffer wieder rückgängig
|
||||||
natürlich nicht die maximale Anzahl der Umlegungen überschritten werden.
|
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
|
Schließlich werden Paare von jeweils einem hingelegten und einem weggenommenen
|
||||||
ausgeschöpft wurde. Dazu einmal das Beispiel aus der Aufgabenstellung: Die Zahl
|
Stäbchen gebildet, die eine Umlegung ergeben. Theoretisch könnte es passieren,
|
||||||
D24 wird erst mit 3 weggenommenen und 2 hingelegten Stäbchen auf F24 erhöht.
|
dass dabei \enquote{die Darstellung einer Ziffer komplett \enquote{geleert}}
|
||||||
Danach kann man allerdings das eine Stäbchen nicht bei den Ziffern 4 oder 2
|
wird. Praktisch tritt dieser Fall bei den gegebenen Beispielen jedoch nicht ein.
|
||||||
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
|
Sei $n$ die Anzahl der Ziffern, so hat der Algorithmus im besten Fall eine
|
||||||
erhöhen, wobei die erste Ziffer ausgelassen wird, sodass der Algorithmus
|
Laufzeit von $O(n) = n$, wenn keine Wiederholung notwendig ist. Dies ist für die
|
||||||
schließlich zur Lösung EE4 kommt.
|
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}
|
\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}
|
\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}
|
\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}
|
\end{document}
|
||||||
|
|||||||
1
Aufgabe3-HexMax/ergebnisdateien/0.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/0.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: EE4
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/1.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: FFFEA97B55
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/2.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/2.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: FFFFFFFFFFFFFFFFD9A9BEAEE8EDA8BDA989D9F8
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/3.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA98BB8B9DFAFEAE888DD888AD8BA8EA8888
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/4.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/4.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB8DE88BAA8ADD888898E9BA88AD98988F898AB7AF7BDA8A61BA7D4AD8F888
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/5.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/5.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88EFA9EBE89EFA99FBDAA8E8EAD88AB899F8E8F9AA9E9AD88988EDA9A99888EDAD989A8BAFD8A888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
|
||||||
1
Aufgabe3-HexMax/ergebnisdateien/negativBeispiel.txt
Normal file
1
Aufgabe3-HexMax/ergebnisdateien/negativBeispiel.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
größte Hex-Zahl: ED8
|
||||||
@ -1,32 +1,51 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
// Struktur für eine veränderte Ziffer
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct Move {
|
struct Move {
|
||||||
digit: u8,
|
digit: u8,
|
||||||
swaps: PutTaken,
|
swaps: PutTaken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enthält den ursprünglichen Wert der Ziffer und den aktuellen
|
||||||
pub struct DigitHistory {
|
pub struct DigitHistory {
|
||||||
pub original_digit: u8,
|
pub original_digit: u8,
|
||||||
move_option: Option<Move>,
|
move_option: Option<Move>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// zur Angabe welche Stäbchen verändert wurden
|
||||||
pub struct ChangedSticks {
|
pub struct ChangedSticks {
|
||||||
pub digit_i: usize,
|
pub digit_i: usize,
|
||||||
pub stick_map: u8,
|
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)]
|
#[derive(Clone, Copy, derive_more::SubAssign, derive_more::AddAssign, derive_more::Add)]
|
||||||
struct PutTaken {
|
struct PutTaken {
|
||||||
put: u16,
|
put: u16,
|
||||||
taken: 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 {
|
struct ChangeDigitsControl {
|
||||||
change_digit: bool,
|
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,
|
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] = [
|
pub const HEX: [u8; 16] = [
|
||||||
0b1110111, // 0
|
0b1110111, // 0
|
||||||
0b0010010, // 1
|
0b0010010, // 1
|
||||||
@ -46,32 +65,57 @@ pub const HEX: [u8; 16] = [
|
|||||||
0b1101100, // F
|
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<DigitHistory>, u16) {
|
pub fn largest_hex(filepath: &str) -> (Vec<DigitHistory>, u16) {
|
||||||
let mut digits: Vec<DigitHistory>;
|
let mut digits: Vec<DigitHistory>; // enthält die Ziffern und wie sie umgelegt wurden
|
||||||
let max_swaps: u16;
|
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 mut lines = content.lines();
|
||||||
let line = lines.next().unwrap();
|
let first_line = lines.next().unwrap();
|
||||||
digits = Vec::with_capacity(line.len());
|
digits = Vec::with_capacity(first_line.len()); // die Länge der ersten Zeile ist die Anzahl der Ziffern
|
||||||
for character in line.chars() {
|
for character in first_line.chars() { // für jedes Zeichen der ersten Zeile
|
||||||
digits.push(DigitHistory {
|
digits.push(DigitHistory {
|
||||||
|
|
||||||
|
// konvertiert die Ziffer in binäre Repräsentation
|
||||||
original_digit: character.to_digit(16).unwrap() as u8,
|
original_digit: character.to_digit(16).unwrap() as u8,
|
||||||
move_option: None,
|
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};
|
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;}
|
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, |_, _, _| {
|
total_swaps = change_digits(digits.iter_mut(), total_swaps, max_swaps, (), true, |_, _, _| {
|
||||||
ChangeDigitsControl {change_digit: true, break_free: 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 {
|
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();
|
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()
|
let change_digit = new_diff < (total_swaps.put as i16 - total_swaps.taken as i16).abs()
|
||||||
&& new_diff < *best_diff;
|
&& new_diff < *best_diff;
|
||||||
if change_digit {*best_diff = new_diff;}
|
if change_digit {*best_diff = new_diff;}
|
||||||
@ -79,11 +123,11 @@ pub fn largest_hex(filepath: &str) -> (Vec<DigitHistory>, u16) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("taken {}, put {}", total_swaps.taken, total_swaps.put);
|
|
||||||
(digits, max_swaps)
|
(digits, max_swaps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// konvertiert eine Slice von Ziffern zu einem hexadezimalen String
|
||||||
pub fn to_hex_str(digits: &[DigitHistory]) -> String {
|
pub fn to_hex_str(digits: &[DigitHistory]) -> String {
|
||||||
let mut largest_hex_str = String::with_capacity(digits.len());
|
let mut largest_hex_str = String::with_capacity(digits.len());
|
||||||
for digit in digits {
|
for digit in digits {
|
||||||
@ -92,19 +136,34 @@ pub fn to_hex_str(digits: &[DigitHistory]) -> String {
|
|||||||
largest_hex_str
|
largest_hex_str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generiert die konkreten Umlegungen
|
||||||
pub fn gen_swaps(digits: &[DigitHistory], number_of_swaps: usize) -> (Vec<ChangedSticks>, Vec<ChangedSticks>) {
|
pub fn gen_swaps(digits: &[DigitHistory], number_of_swaps: usize) -> (Vec<ChangedSticks>, Vec<ChangedSticks>) {
|
||||||
|
|
||||||
|
// Stäbchen, die hingelegt wurden
|
||||||
let mut moves_put: Vec<ChangedSticks> = Vec::with_capacity(number_of_swaps);
|
let mut moves_put: Vec<ChangedSticks> = Vec::with_capacity(number_of_swaps);
|
||||||
|
|
||||||
|
// Stäbchen, die weggenommenen wurden
|
||||||
let mut moves_taken: Vec<ChangedSticks> = Vec::with_capacity(number_of_swaps);
|
let mut moves_taken: Vec<ChangedSticks> = Vec::with_capacity(number_of_swaps);
|
||||||
|
|
||||||
|
// für jede Ziffer
|
||||||
for (j, DigitHistory {original_digit, move_option}) in digits.iter().enumerate() {
|
for (j, DigitHistory {original_digit, move_option}) in digits.iter().enumerate() {
|
||||||
if let Some(moved) = move_option {
|
if let Some(moved) = move_option { // Wenn die Ziffer geändert wurde
|
||||||
let from_hex = HEX[*original_digit as usize];
|
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 to_hex = HEX[moved.digit as usize];
|
||||||
let changed = from_hex ^ to_hex;
|
let changed = from_hex ^ to_hex;
|
||||||
let put = changed & to_hex;
|
let put = changed & to_hex; // Bitmap der hingelegten Stäbchen
|
||||||
let taken = changed & from_hex;
|
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)] {
|
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 {
|
for i in 0..u8::BITS {
|
||||||
let stick_map = put_or_taken & 1 << i;
|
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 {
|
if stick_map != 0 {
|
||||||
moves_put_or_taken.push(ChangedSticks {
|
moves_put_or_taken.push(ChangedSticks {
|
||||||
digit_i: j,
|
digit_i: j,
|
||||||
@ -118,31 +177,55 @@ pub fn gen_swaps(digits: &[DigitHistory], number_of_swaps: usize) -> (Vec<Change
|
|||||||
(moves_put, moves_taken)
|
(moves_put, moves_taken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ändert die Ziffern mithilfe eines Iterators über diese. Eine Lambdafunktion evaluiert, ob die
|
||||||
|
* Ziffer geändert werden soll.
|
||||||
|
* init_t ist der Wert, auf den die Variable initialisiert wird, die der Lambdafunktion übergeben
|
||||||
|
* wird.
|
||||||
|
* Wenn higher_digit true ist, wird nur probiert, die Ziffer zu erhöhen. */
|
||||||
fn change_digits<'a, I: Iterator<Item = &'a mut DigitHistory>, T: Copy,
|
fn change_digits<'a, I: Iterator<Item = &'a mut DigitHistory>, T: Copy,
|
||||||
F: Fn(PutTaken, PutTaken, &mut T) -> ChangeDigitsControl>
|
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 {
|
(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 {
|
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;}
|
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;
|
let mut swaps_with_undo = total_swaps;
|
||||||
if let Some(moved) = move_option {
|
if let Some(moved) = move_option {
|
||||||
swaps_with_undo -= moved.swaps;
|
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<Move> = None;
|
let mut best_move: Option<Move> = None;
|
||||||
let mut t = init_t;
|
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() {
|
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 j as u8 == *original_digit {
|
||||||
if higher_digit {break;}
|
if higher_digit {break;}
|
||||||
else {continue;}
|
else {continue;}
|
||||||
}
|
}
|
||||||
let changed_sticks = digit_hex ^ *to_digit;
|
let changed_sticks = digit_hex ^ *to_digit;
|
||||||
|
|
||||||
|
// Anzahl der neu hingelegten und weggenommenen Stäbchen
|
||||||
let swaps = PutTaken {
|
let swaps = PutTaken {
|
||||||
put: (changed_sticks & *to_digit).count_ones() as u16,
|
put: (changed_sticks & *to_digit).count_ones() as u16,
|
||||||
taken: (changed_sticks & digit_hex).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;
|
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 {
|
if new_total_swaps.put <= max_swaps && new_total_swaps.taken <= max_swaps {
|
||||||
let control = run(new_total_swaps, total_swaps, &mut t);
|
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 {
|
if control.change_digit {
|
||||||
best_move = Some(Move {
|
best_move = Some(Move {
|
||||||
digit: j as u8,
|
digit: j as u8,
|
||||||
@ -152,6 +235,7 @@ fn change_digits<'a, I: Iterator<Item = &'a mut DigitHistory>, T: Copy,
|
|||||||
if control.break_free {break;}
|
if control.break_free {break;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// führt die Änderung der Ziffer durch
|
||||||
if let Some(moved) = best_move {
|
if let Some(moved) = best_move {
|
||||||
*move_option = best_move;
|
*move_option = best_move;
|
||||||
total_swaps = swaps_with_undo + moved.swaps;
|
total_swaps = swaps_with_undo + moved.swaps;
|
||||||
|
|||||||
@ -13,40 +13,71 @@ struct Args {
|
|||||||
/// Ausgabe der Anzahl der Umlegungen
|
/// Ausgabe der Anzahl der Umlegungen
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
number_of_swaps_print: bool,
|
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() {
|
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);
|
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.number_of_swaps_print {println!("Anzahl der Umlegungen: {}", number_of_swaps);}
|
||||||
if args.swap_print {
|
if args.swap_print {
|
||||||
|
|
||||||
|
// generiert die Umlegungen
|
||||||
let (moves_put, moves_taken) = hexmax::gen_swaps(&digits, number_of_swaps as usize);
|
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<u8> = digits.iter()
|
let mut digits_hex: Vec<u8> = digits.iter()
|
||||||
.map(|digit| hexmax::HEX[digit.original_digit as usize]).collect();
|
.map(|digit| hexmax::HEX[digit.original_digit as usize]).collect();
|
||||||
|
|
||||||
println!("Umlegungen:");
|
println!("Umlegungen:");
|
||||||
swap_print(&digits_hex);
|
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!();
|
println!();
|
||||||
let mut not_first = false;
|
|
||||||
for (move_put, move_taken) in moves_put.iter().zip(&moves_taken) {
|
// bettet jedes ASCII-Art extra ein, damit kein Seitenumbruch in einem ASCII-Art ist
|
||||||
if not_first {println!();}
|
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] {
|
for hexmax::ChangedSticks {digit_i, stick_map} in [move_put, move_taken] {
|
||||||
digits_hex[*digit_i] ^= stick_map;
|
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]) {
|
// Hilfsfunktion für die Ausgabe der Zahlen. Sie ruft für jede Zahl die Lambdafunktion auf.
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_helper<F: Fn(u8)>(digits_hex: &[u8], run: F) {
|
fn print_helper<F: Fn(u8)>(digits_hex: &[u8], run: F) {
|
||||||
let mut not_first = false;
|
let mut not_first = false;
|
||||||
for digit in digits_hex {
|
for digit in digits_hex {
|
||||||
@ -57,16 +88,28 @@ fn print_helper<F: Fn(u8)>(digits_hex: &[u8], run: F) {
|
|||||||
println!();
|
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 {
|
fn print_stick(digit: u8, i: u8, stick_char: char) -> char {
|
||||||
if digit >> i & 1 == 1 {stick_char} else {' '}
|
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) {
|
fn print_horizontal(digits_hex: &[u8], i: u8) {
|
||||||
print_helper(digits_hex, |digit|
|
print_helper(digits_hex, |digit|
|
||||||
print!("{} {}", print_stick(digit, i, '|'), print_stick(digit, i - 1, '|'))
|
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) {
|
fn print_vertical(digits_hex: &[u8], i: u8) {
|
||||||
print_helper(digits_hex, |digit| print!(" {} ", print_stick(digit, i, '_')));
|
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}");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user