From 95480f78e4ca5df9c323c83db80bbb8d79e2dfda Mon Sep 17 00:00:00 2001 From: MrGeorgen Date: Tue, 15 Feb 2022 21:24:05 +0100 Subject: [PATCH] a2 draft --- .gitignore | 1 + Aufgabe2-Rechenrätsel/doc.tex | 201 ++++++++++--- Aufgabe2-Rechenrätsel/ergebnis-latex.sh | 5 + Aufgabe2-Rechenrätsel/ergebnisdateien/1.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/10.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/11.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/12.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/13.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/14.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/15.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/16.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/17.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/18.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/19.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/2.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/20.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/21.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/22.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/3.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/4.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/5.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/6.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/7.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/8.txt | 2 + Aufgabe2-Rechenrätsel/ergebnisdateien/9.txt | 2 + Aufgabe2-Rechenrätsel/rust-toolchain | 1 + Aufgabe2-Rechenrätsel/src/main.rs | 279 ++++++++++++++----- 27 files changed, 432 insertions(+), 99 deletions(-) create mode 100755 Aufgabe2-Rechenrätsel/ergebnis-latex.sh create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/1.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/10.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/11.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/12.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/13.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/14.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/15.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/16.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/17.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/18.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/19.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/2.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/20.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/21.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/22.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/3.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/4.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/5.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/6.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/7.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/8.txt create mode 100644 Aufgabe2-Rechenrätsel/ergebnisdateien/9.txt create mode 100644 Aufgabe2-Rechenrätsel/rust-toolchain diff --git a/.gitignore b/.gitignore index c3870e4..3b43f71 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ beispieldaten/ *.log *.pdf *.toc +_minted-doc/ diff --git a/Aufgabe2-Rechenrätsel/doc.tex b/Aufgabe2-Rechenrätsel/doc.tex index 0eaedb6..313f136 100644 --- a/Aufgabe2-Rechenrätsel/doc.tex +++ b/Aufgabe2-Rechenrätsel/doc.tex @@ -1,8 +1,9 @@ \documentclass[a4paper,10pt,ngerman]{scrartcl} \usepackage{babel} \usepackage[T1]{fontenc} -\usepackage[utf8x]{inputenc} +\usepackage[utf8]{inputenc} \usepackage[a4paper,margin=2.5cm,footskip=0.5cm]{geometry} +\DeclareUnicodeCharacter{25CB}{$\circ$} % Die nächsten drei Felder bitte anpassen: \newcommand{\Aufgabe}{Aufgabe 2: Rechenrätsel} % Aufgabennummer und Aufgabennamen angeben @@ -32,28 +33,11 @@ \usepackage{algpseudocode} % Für Quelltext -\usepackage{listings} +\usepackage{minted} \usepackage{color} \definecolor{mygreen}{rgb}{0,0.6,0} \definecolor{mygray}{rgb}{0.5,0.5,0.5} \definecolor{mymauve}{rgb}{0.58,0,0.82} -\lstset{ - keywordstyle=\color{blue},commentstyle=\color{mygreen}, - stringstyle=\color{mymauve},rulecolor=\color{black}, - basicstyle=\footnotesize\ttfamily,numberstyle=\tiny\color{mygray}, - captionpos=b, % sets the caption-position to bottom - keepspaces=true, % keeps spaces in text - numbers=left, numbersep=5pt, showspaces=false,showstringspaces=true, - showtabs=false, stepnumber=2, tabsize=2, title=\lstname -} -\lstdefinelanguage{JavaScript}{ % JavaScript ist als einzige Sprache noch nicht vordefiniert - keywords={break, case, catch, continue, debugger, default, delete, do, else, finally, for, function, if, in, instanceof, new, return, switch, this, throw, try, typeof, var, void, while, with}, - morecomment=[l]{//}, - morecomment=[s]{/*}{*/}, - morestring=[b]', - morestring=[b]", - sensitive=true -} % Anführungszeichen \usepackage{csquotes} @@ -77,14 +61,62 @@ \vspace{0.5cm} \section{Lösungsidee} -Die Aufgabenstellung gibt nicht vor wie ein Rätsel, das \enquote{interessant und -unterschiedlich} ist, zu sein hat. Damit das Rätsel interessant ist habe ich mir -folgende Regeln überlegt: +\subsection{Allgemeines} +Der Beweis, ob ein Rechenrätsel eindeutig lösbar ist, ist ein +Entscheidungsproblem mit NP-Schwere. Eine 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 +gibt und das Programm somit nie zu einem Ergebnis kommt. Allerdings läuft die +Wahrscheinlichkeit dafür gegen null. Solch eine Brute-Force +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, +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. + +\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: +\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: \begin{enumerate} \item Bei $n$ Operatoren muss für $m_i$, die Anzahl, mit der jeder der vier Operatoren vorkommt, gelten: \begin{align} - \frac{n}{10} - 1 < m_i \leq \frac{n}{3} + 1 + \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: @@ -92,24 +124,119 @@ folgende Regeln überlegt: \frac{n}{16} - 1 < m_i \leq \frac{n}{4} + 1 \end{align} \end{enumerate} - -Der Beweis, ob ein Rechenrätsel eindeutig lösbar ist, ist ein -Entscheidungsproblem mit NP-Schwere. Eine Lösung ist die Ziffern 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. Solch eine Brute-Force Operation dauert allerdings bei -viele Operatoren exponentiell länger. Die Anzahl der Möglichkeiten $|\Omega|$ -kann in Abhängigkeit von der Operatorenanzahl $n$ berechnet werden: -$|\Omega|=4^{n}$. 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. +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. \section{Umsetzung} -Hier wird kurz erläutert, wie die Lösungsidee im Programm tatsächlich umgesetzt wurde. Hier können auch Implementierungsdetails erwähnt werden. +\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 +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: +\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. + 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 + eines bestimmten Integer-Typs zu einen generischen Integer-Typ + zuständig. num-derive stellt Macros zur Verfügung, die es + erlauben Integers zu Enums zu konvertieren. Mit num-integer + können arithmetischen Operationen generisch für verschiedene + Integer-Typen implementiert werden. +\end{itemize} + +\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 +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 +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 +Operanden angibt, werden diese zufällig ausgewählt. Dabei befinden sich die +Ziffern in einem Vector (Array mit dynamischer Größe) 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 +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. + +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. + +In der Schleife wird die Funktion \mintinline{rs}|multiplicate_divide| mit einer Sequenz von +Operanden, zwischen denen nur Punktrechnung vorkommt, aufgerufen. Diese ruft +sich selber doppelt rekursiv auf, wobei einmal multipliziert und einmal +dividiert wird. Die Division wird ausgelassen, wenn das Zwischenergebnis keine +ganze Zahl ist. Wenn das Ende der Sequenz erreicht ist, werden die verwendeten +Operatoren in einer Hashmap mit dem Zwischenergebnis als Schüssel abgespeichert. +Wenn das Zwischenergebnis bereits einmal vorkam, wird unter dem Ergebnis +\mintinline{rs}|None| abspeichert und das Zwischenergebnis somit als uneindeutig +markiert. Die Hashmaps mit den möglichen Zwischenergebnissen werden zusammen in +einem Vector \mintinline{rs}|results_multiplicate| gespeichert. Zusätzlich +werden die Operanden, die nicht Teil einer Punktrechnungssequenz sind, einfach +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 +sich die Funktion pro Durchlauf zweimal auf, wobei einmal addiert und einmal +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 +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 +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 +\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 +while-Schleife der main-Funktion noch einmal durchlaufen werden. \section{Beispiele} -Genügend Beispiele einbinden! Die Beispiele von der BwInf-Webseite sollten hier diskutiert werden, aber auch eigene Beispiele sind sehr gut – besonders wenn sie Spezialfälle abdecken. Aber bitte nicht 30 Seiten Programmausgabe hier einfügen! +\immediate\write18{./ergebnis-latex.sh} +\input{ausgabe.tmp} +\immediate\write18{rm ausgabe.tmp} \section{Quellcode} Unwichtige Teile des Programms sollen hier nicht abgedruckt werden. Dieser Teil sollte nicht mehr als 2–3 Seiten umfassen, maximal 10. diff --git a/Aufgabe2-Rechenrätsel/ergebnis-latex.sh b/Aufgabe2-Rechenrätsel/ergebnis-latex.sh new file mode 100755 index 0000000..02c5f00 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnis-latex.sh @@ -0,0 +1,5 @@ +#/bin/sh +for file in $(ls -v ergebnisdateien); do + operators=$(basename "$file" .txt) + printf "%s Operatoren:\\inputminted[breaklines]{text}{%s}\n" "$operators" "ergebnisdateien/$file" >> ausgabe.tmp +done diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/1.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/1.txt new file mode 100644 index 0000000..5bbedef --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/1.txt @@ -0,0 +1,2 @@ +Rätsel: 1 ○ 3 = 3 +Lösung: 1 * 3 = 3 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/10.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/10.txt new file mode 100644 index 0000000..22570b6 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/10.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 2 ○ 6 ○ 2 ○ 3 ○ 3 ○ 5 ○ 1 ○ 6 ○ 7 ○ 5 = 296 +Lösung: 2 : 2 - 6 + 2 * 3 * 3 * 5 + 1 + 6 * 7 * 5 = 296 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/11.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/11.txt new file mode 100644 index 0000000..a89db54 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/11.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 7 ○ 3 ○ 5 ○ 5 ○ 1 ○ 8 ○ 3 ○ 2 ○ 4 ○ 5 ○ 8 = 698 +Lösung: 2 + 7 * 3 * 5 * 5 - 1 + 8 * 3 : 2 + 4 * 5 * 8 = 698 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/12.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/12.txt new file mode 100644 index 0000000..2c6559f --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/12.txt @@ -0,0 +1,2 @@ +Rätsel: 9 ○ 3 ○ 7 ○ 3 ○ 3 ○ 6 ○ 2 ○ 9 ○ 9 ○ 9 ○ 6 ○ 6 ○ 1 = 1888 +Lösung: 9 * 3 * 7 * 3 * 3 + 6 * 2 * 9 + 9 * 9 - 6 : 6 - 1 = 1888 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/13.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/13.txt new file mode 100644 index 0000000..70eaa5b --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/13.txt @@ -0,0 +1,2 @@ +Rätsel: 9 ○ 4 ○ 8 ○ 4 ○ 3 ○ 8 ○ 6 ○ 6 ○ 9 ○ 5 ○ 4 ○ 6 ○ 9 ○ 6 = 3976 +Lösung: 9 - 4 + 8 * 4 * 3 : 8 * 6 * 6 * 9 + 5 + 4 * 6 + 9 * 6 = 3976 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/14.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/14.txt new file mode 100644 index 0000000..1332597 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/14.txt @@ -0,0 +1,2 @@ +Rätsel: 7 ○ 8 ○ 1 ○ 8 ○ 5 ○ 6 ○ 7 ○ 8 ○ 2 ○ 4 ○ 5 ○ 1 ○ 7 ○ 4 ○ 8 = 3093 +Lösung: 7 - 8 - 1 - 8 * 5 + 6 * 7 * 8 : 2 * 4 * 5 - 1 - 7 * 4 * 8 = 3093 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/15.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/15.txt new file mode 100644 index 0000000..870f084 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/15.txt @@ -0,0 +1,2 @@ +Rätsel: 4 ○ 5 ○ 5 ○ 5 ○ 7 ○ 2 ○ 8 ○ 6 ○ 8 ○ 6 ○ 8 ○ 6 ○ 3 ○ 5 ○ 9 ○ 1 = 8227 +Lösung: 4 * 5 * 5 * 5 * 7 : 2 - 8 + 6 - 8 + 6 + 8 * 6 * 3 * 5 * 9 + 1 = 8227 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/16.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/16.txt new file mode 100644 index 0000000..c62ba98 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/16.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 9 ○ 1 ○ 3 ○ 2 ○ 9 ○ 4 ○ 8 ○ 1 ○ 6 ○ 2 ○ 4 ○ 9 ○ 8 ○ 4 ○ 5 ○ 7 = 17044 +Lösung: 2 - 9 + 1 - 3 * 2 * 9 * 4 - 8 + 1 + 6 : 2 * 4 * 9 * 8 * 4 * 5 - 7 = 17044 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/17.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/17.txt new file mode 100644 index 0000000..caa72c5 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/17.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 5 ○ 6 ○ 9 ○ 4 ○ 9 ○ 7 ○ 7 ○ 4 ○ 6 ○ 1 ○ 9 ○ 3 ○ 5 ○ 8 ○ 3 ○ 5 ○ 4 = 28009 +Lösung: 2 - 5 + 6 * 9 * 4 : 9 * 7 * 7 * 4 * 6 + 1 - 9 - 3 * 5 * 8 : 3 * 5 - 4 = 28009 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/18.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/18.txt new file mode 100644 index 0000000..2671ad4 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/18.txt @@ -0,0 +1,2 @@ +Rätsel: 6 ○ 9 ○ 9 ○ 6 ○ 8 ○ 9 ○ 4 ○ 5 ○ 9 ○ 5 ○ 5 ○ 1 ○ 9 ○ 3 ○ 2 ○ 8 ○ 4 ○ 3 ○ 7 = 32686 +Lösung: 6 + 9 * 9 * 6 * 8 * 9 - 4 - 5 - 9 - 5 * 5 - 1 - 9 * 3 * 2 * 8 : 4 * 3 * 7 = 32686 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/19.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/19.txt new file mode 100644 index 0000000..09a6618 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/19.txt @@ -0,0 +1,2 @@ +Rätsel: 7 ○ 3 ○ 1 ○ 6 ○ 3 ○ 7 ○ 2 ○ 7 ○ 1 ○ 4 ○ 3 ○ 5 ○ 7 ○ 6 ○ 8 ○ 7 ○ 7 ○ 8 ○ 8 ○ 9 = 219070 +Lösung: 7 + 3 - 1 - 6 * 3 * 7 : 2 * 7 - 1 + 4 * 3 * 5 * 7 : 6 * 8 * 7 * 7 * 8 - 8 - 9 = 219070 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/2.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/2.txt new file mode 100644 index 0000000..e84c10e --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/2.txt @@ -0,0 +1,2 @@ +Rätsel: 1 ○ 7 ○ 8 = 2 +Lösung: 1 - 7 + 8 = 2 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/20.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/20.txt new file mode 100644 index 0000000..b89a3c0 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/20.txt @@ -0,0 +1,2 @@ +Rätsel: 4 ○ 9 ○ 3 ○ 1 ○ 2 ○ 8 ○ 9 ○ 7 ○ 8 ○ 6 ○ 9 ○ 5 ○ 2 ○ 3 ○ 9 ○ 8 ○ 7 ○ 6 ○ 5 ○ 2 ○ 1 = 52647 +Lösung: 4 - 9 * 3 - 1 - 2 - 8 * 9 * 7 * 8 + 6 + 9 * 5 * 2 * 3 * 9 * 8 * 7 : 6 * 5 : 2 - 1 = 52647 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/21.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/21.txt new file mode 100644 index 0000000..73fc060 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/21.txt @@ -0,0 +1,2 @@ +Rätsel: 3 ○ 7 ○ 8 ○ 6 ○ 8 ○ 2 ○ 5 ○ 4 ○ 1 ○ 6 ○ 6 ○ 2 ○ 2 ○ 7 ○ 3 ○ 2 ○ 9 ○ 8 ○ 5 ○ 7 ○ 6 ○ 1 = 49785 +Lösung: 3 + 7 * 8 * 6 * 8 : 2 + 5 + 4 - 1 + 6 * 6 * 2 * 2 * 7 : 3 * 2 * 9 * 8 + 5 + 7 * 6 - 1 = 49785 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/22.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/22.txt new file mode 100644 index 0000000..4f9dcca --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/22.txt @@ -0,0 +1,2 @@ +Rätsel: 3 ○ 8 ○ 2 ○ 8 ○ 2 ○ 5 ○ 4 ○ 8 ○ 8 ○ 5 ○ 7 ○ 4 ○ 5 ○ 5 ○ 4 ○ 2 ○ 5 ○ 3 ○ 3 ○ 4 ○ 6 ○ 1 ○ 9 = 471837 +Lösung: 3 - 8 - 2 - 8 : 2 * 5 * 4 * 8 - 8 + 5 * 7 * 4 * 5 * 5 * 4 : 2 * 5 * 3 * 3 : 4 * 6 + 1 - 9 = 471837 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/3.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/3.txt new file mode 100644 index 0000000..48dbeab --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/3.txt @@ -0,0 +1,2 @@ +Rätsel: 4 ○ 4 ○ 5 ○ 1 = 2 +Lösung: 4 + 4 - 5 - 1 = 2 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/4.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/4.txt new file mode 100644 index 0000000..a027dc8 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/4.txt @@ -0,0 +1,2 @@ +Rätsel: 7 ○ 2 ○ 5 ○ 9 ○ 2 = 1 +Lösung: 7 * 2 + 5 - 9 * 2 = 1 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/5.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/5.txt new file mode 100644 index 0000000..485a082 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/5.txt @@ -0,0 +1,2 @@ +Rätsel: 7 ○ 4 ○ 2 ○ 8 ○ 8 ○ 3 = 36 +Lösung: 7 * 4 - 2 * 8 + 8 * 3 = 36 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/6.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/6.txt new file mode 100644 index 0000000..0227e8a --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/6.txt @@ -0,0 +1,2 @@ +Rätsel: 5 ○ 6 ○ 3 ○ 7 ○ 3 ○ 1 ○ 1 = 41 +Lösung: 5 * 6 + 3 + 7 + 3 - 1 - 1 = 41 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/7.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/7.txt new file mode 100644 index 0000000..c83645d --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/7.txt @@ -0,0 +1,2 @@ +Rätsel: 1 ○ 9 ○ 3 ○ 9 ○ 5 ○ 8 ○ 3 ○ 3 = 92 +Lösung: 1 * 9 - 3 + 9 + 5 + 8 * 3 * 3 = 92 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/8.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/8.txt new file mode 100644 index 0000000..ccaffb5 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/8.txt @@ -0,0 +1,2 @@ +Rätsel: 2 ○ 4 ○ 9 ○ 7 ○ 3 ○ 9 ○ 8 ○ 1 ○ 8 = 125 +Lösung: 2 * 4 * 9 - 7 - 3 + 9 * 8 - 1 - 8 = 125 diff --git a/Aufgabe2-Rechenrätsel/ergebnisdateien/9.txt b/Aufgabe2-Rechenrätsel/ergebnisdateien/9.txt new file mode 100644 index 0000000..b7e060b --- /dev/null +++ b/Aufgabe2-Rechenrätsel/ergebnisdateien/9.txt @@ -0,0 +1,2 @@ +Rätsel: 8 ○ 5 ○ 4 ○ 7 ○ 8 ○ 3 ○ 6 ○ 3 ○ 8 ○ 3 = 285 +Lösung: 8 * 5 * 4 + 7 * 8 + 3 - 6 + 3 * 8 * 3 = 285 diff --git a/Aufgabe2-Rechenrätsel/rust-toolchain b/Aufgabe2-Rechenrätsel/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/Aufgabe2-Rechenrätsel/src/main.rs b/Aufgabe2-Rechenrätsel/src/main.rs index 5c23403..427da5b 100644 --- a/Aufgabe2-Rechenrätsel/src/main.rs +++ b/Aufgabe2-Rechenrätsel/src/main.rs @@ -1,18 +1,21 @@ -#![feature(trait_alias)] +#![feature(trait_alias)] // aktiviert Trait Aliase +// Importe use clap::Parser; -use std::collections::HashSet; -use std::collections::HashMap; +use std::collections::{HashSet, HashMap}; use rand::distributions::{Distribution, Uniform}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -use num_traits::NumCast; 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; -#[derive(FromPrimitive)] +// Enum für die vier Grundoperatoren +#[derive(FromPrimitive)] // Durch FromPrimitive können ganze Zahlen zu Enums konvertiert werden. enum Operator { Add, Subtract, @@ -20,19 +23,17 @@ enum Operator { Divide, } +// Enum für Punkt- oder Strichrechnung #[derive(FromPrimitive, PartialEq, Clone, Copy)] enum AddSubOrMultiplicateDivide { AddSub, MultiplicateDivide, } +// Struktur für die Kommandozeilenargumente #[derive(Parser)] -#[clap(author, version, about, long_about = None)] +#[clap(about, long_about = None)] struct Args { - /// maximale Anzahl der auszugebenden Lösungen - #[clap(short, long, default_value_t = 1)] - maximum: usize, - /// Anzahl der Operatoren #[clap(short, long, default_value_t = 5)] count: u8, @@ -40,107 +41,203 @@ struct Args { /// zeigt die Lösung an #[clap(short, long, takes_value = false)] 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, + last_operator: Option, // der Operator, der vor dieser Rechnungsteil steht } +// Struktur, die eindeutige Ergebnisse speichert struct ResultStore { - riddles: HashMap, + 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{ + 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)} + 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); + 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(); - let mut digits: Vec = vec!(0; (args.count + 1) as usize); - { - let mut selectable_digits: Vec = Vec::from_iter(0..9); - let mut digits_count: [u8; 9] = [0; 9]; - let min: u8 = digits.len() as u8 / 16; - let max: u8 = digits.len() as u8 / 4 + 1; - let mut left_for_non_min: u8 = digits.len() as u8 - 9 * min; - let mut digits_min_statisfied: HashSet = HashSet::with_capacity(9); - for digit in digits.iter_mut() { - if left_for_non_min == 0 { - selectable_digits.retain(|x| !digits_min_statisfied.contains(x)); - } - 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; - let digit_used = digits_count[rand_number as usize]; - if digit_used == min { - if left_for_non_min == 0 { - selectable_digits.remove(rand_i); - } else {digits_min_statisfied.insert(rand_number);} - } - if digit_used > min {left_for_non_min -= 1;} - if digit_used == max {selectable_digits.remove(rand_i);} - *digit = rand_number + 1; - } + + /* 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.len() == 0; + 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 / 16; // minmale Anzahl der jeweiligen Ziffern + let max: u8 = digits.len() as u8 / 4 + 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;} } - calc_results::(args, digits); } -fn calc_results(args: Args, digits: Vec) { - let mut results = ResultStore::new(); - for dm_as_map in 0..2u32.pow(args.count as u32) { +/* 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 = FromPrimitive::from_u32(dm_as_map & 1).unwrap(); - while last_i < args.count as usize { + let mut state = AddSubOrMultiplicateDivide::from_u32(dm_as_map & 1).unwrap(); + while last_i < number_operators { i = last_i; - while i < args.count as usize && dm_as_map >> i & 1 == state as u32 { + + // 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)); + 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(); - let first_digit = FromPrimitive::from_u8(*digits_calc.next().unwrap()).unwrap(); + 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; } - if dm_as_map >> args.count - 1 & 1 == AddSubOrMultiplicateDivide::AddSub as u32 { + /* 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); @@ -148,62 +245,115 @@ fn calc_results(args: Args, } } } - let mut not_first = false; + 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 { - if not_first {println!();} - not_first = true; - let result_u128 = num_traits::cast(result).unwrap(); - print_riddle(&digits, false, result_u128, operators); - if args.solution_print { - print_riddle(&digits, true, result_u128, operators); + 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. + print_riddle(digits, false, smallest_result, operators); + if solution_print { // Die Ausgabe der Lösung ist optional. + print_riddle(digits, true, smallest_result, operators); + } + 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 = FromPrimitive::from_u8(*next).unwrap(); + 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 } - _ => T::zero(), + /* 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()) } @@ -214,12 +364,16 @@ results: &mut ResultStore, part_result: T, operators: Option) { } } +// 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)} } -fn insert_digit(digits: &Vec, i: usize) -> PartOperation { +// 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)); return PartOperation{ results: result_map, @@ -227,7 +381,8 @@ fn insert_digit(digits: &Vec, i: usize) -> Par }; } -fn print_riddle(digits: &Vec, show_solution: bool, result: u128, operators: u64) { +// gibt das Rätsel bzw. die Lösung des Rätsels aus +fn print_riddle(digits: &[u8], show_solution: bool, result: T, operators: u64) { print!("{}: ", if show_solution {"Lösung"} else {"Rätsel"}); let mut i = 0; for digit in digits[.. digits.len() - 1].iter() {