\documentclass[a4paper,10pt,ngerman]{scrartcl} \usepackage{babel} \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage[a4paper,margin=2.5cm,footskip=0.5cm]{geometry} % Die nächsten drei Felder bitte anpassen: \newcommand{\Aufgabe}{Aufgabe 3: HexMax} % Aufgabennummer und Aufgabennamen angeben \newcommand{\TeilnahmeId}{60813} % Teilnahme-ID angeben \newcommand{\Name}{Marcel Zinkel} % Name des Bearbeiter / der Bearbeiterin dieser Aufgabe angeben % Kopf- und Fußzeilen \usepackage{scrlayer-scrpage, lastpage} \setkomafont{pageheadfoot}{\large\textrm} \lohead{\Aufgabe} \rohead{Teilnahme-ID: \TeilnahmeId} \cfoot*{\thepage{}/\pageref{LastPage}} % Position des Titels \usepackage{titling} \setlength{\droptitle}{-1.0cm} % Für mathematische Befehle und Symbole \usepackage{amsmath} \usepackage{amssymb} % Für Bilder \usepackage{graphicx} % Für Algorithmen \usepackage{algpseudocode} % Für Quelltext \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} % Anführungszeichen \usepackage{csquotes} % Diese beiden Pakete müssen zuletzt geladen werden %\usepackage{hyperref} % Anklickbare Links im Dokument \usepackage{cleveref} \usepackage{forloop} \newcommand{\beispiel}[1] { hexmax#1.txt: \input{|target/debug/hexmax -s -l -d 17 beispieldaten/hexmax#1.txt} } % Daten für die Titelseite \title{\textbf{\Huge\Aufgabe}} \author{\LARGE Teilnahme-ID: \LARGE \TeilnahmeId \\\\ \LARGE Bearbeiter/-in dieser Aufgabe: \\ \LARGE \Name\\\\} \date{\LARGE\today} \begin{document} \maketitle \tableofcontents \vspace{0.5cm} \section{Lösungsidee} Durch ebenso häufiges Hinlegen und Wegnehmen der Stäbchen erhält man eine Hex-Zahl, die man auch durch Umlegen der Stäbchen erreichen kann. Der Algorithmus kann also erstmal mehr Stäbchen hinlegen wie wegnehmen oder umgekehrt, solange am Ende die Anzahl der hingelegten Stäbchen gleich der der weggenommenen Stäbchen ist. Die größte Zahl erhält man, wenn man möglichst die vorderen Ziffern erhöht. Der Algorithmus geht, angefangen mit der ersten Ziffer, jede Ziffer der Zahl durch. Davon ausgenommen sind Ziffern, die schon geändert wurden. Bei jeder Ziffer, sofern sie nicht schon F ist, wird probiert sie auf F,E,... umzulegen. Dabei wird gezählt, wie viele Stäbchen weggenommenen und hingelegt werden müssen. Überschreitet weder die Gesamtzahl der weggenommenen Stäbchen, noch die der hingelegten Stäbchen die gegebene maximale Anzahl der Umlegungen, wird die Ziffer entsprechend geändert. Ansonsten wird probiert die Ziffer zu der nächst kleineren Ziffern zu ändern. Dies wird so lange wiederholt, bis die Änderung der Ziffer erfolgreich war oder bereits alle Ziffern, die größer sind, ausprobiert wurden. Danach wird zu der nächsten Ziffer übergegangen. Nachdem die vorderen Ziffern entsprechend erhöht wurden, kann es sein, dass noch Stäbchen benötigt werden oder loszuwerden sind, damit gleich viele Stäbchen weggenommenen wie hingelegt wurden. Da zuvor schon die Ziffern nach Möglichkeit erhöht wurden, kann dies nur erreicht werden, indem Ziffern verringert werden. Damit das nicht so stark ins Gewicht fällt, wird über die Ziffern von hinten iteriert. Ziffer für Ziffer wird nun probiert, die Ziffer so zu ändern, dass sich der Abstand zwischen der Anzahl der hingelegten und der weggenommenen Stäbchen verringert. Es kann auch eine vorherige Änderung der Ziffer wieder rückgängig gemacht werden. Es kommt zu einem Fehler, wenn man bei einer hinteren Ziffer weniger Stäbchen hätte loswerden bzw. wegnehmen sollen, um später bei einer Ziffer, die weiter vorne ist, mehr Stäbchen hinlegen bzw. wegnehmen zu können. Wenn sie dadurch größer würde oder es dadurch überhaupt erst möglich wäre den Ausgleichsvorgang schon bei ihr abzuschließen, ist das Ergebnis falsch. Glücklicherweise tritt dieser Fall bei den gegebenen Beispielen nicht ein. Unter \ref{negativ} findet sich noch ein entsprechendes Negativbeispiel. Auch bei diesem Algorithmusteil darf natürlich nicht die maximale Anzahl der Umlegungen überschritten werden. Der Algorithmus wird so lange wiederholt bis die maximale Anzahl der Umlegungen ausgeschöpft wurde. Schließlich werden Paare von jeweils einem hingelegten und einem weggenommenen Stäbchen gebildet, die eine Umlegung ergeben. Theoretisch könnte es passieren, dass dabei \enquote{die Darstellung einer Ziffer komplett \enquote{geleert}} wird. Praktisch tritt dieser Fall bei den gegebenen Beispielen jedoch nicht ein. Sei $n$ die Anzahl der Ziffern, so hat der Algorithmus im besten Fall eine Laufzeit von $O(n) = n$, wenn keine Wiederholung notwendig ist. Dies ist für die Beispiele 2-5 auch die tatsächliche Laufzeit. Da es maximal $n$ Wiederholung geben kann, ist die Laufzeit im schlechtesten Fall $O(n) = n^2$ \section{Umsetzung} \subsection{Umgebung und Bibliotheken} Die Lösungsidee wird in Rust implementiert. Zum Erleichtern der Implementierung habe ich noch zwei crates\footnote{Abhängigkeiten in Rust} benutzt: \begin{enumerate} \item Mit clap werden die Kommandozeilenargumente verarbeitet. \item derive\_more bietet u. a. die Möglichkeit die einzelnen Attribute einer Struktur jeweils zu addieren oder zu subtrahieren wie bei einem Vektor. \end{enumerate} \subsection{Benutzung} Dem Programm muss als Argument der Dateipfad zu der Eingabedatei übergeben werden. Außerdem können noch weitere Optionen angepasst werden. Mit der Option -d kann die Anzahl der Ziffern, die beim Ausgeben des Zwischenstands der Umlegungen maximal in einer Zeile anzeigt werden sollen, geändert werden. Standardmäßig sind dies 20 Ziffern, da jede Ziffer ein ASCII-Art\footnote{ASCII-Art ist die Darstellung eines Bildes oder Piktogramms mit Zeichen der ASCII-Codierung.} ist und dementsprechend Platz braucht. Beim Verwenden des flags -n wird die Anzahl der durchgeführten Umlegungen ausgegeben. Benutzt man den flag -s, so wird der Zwischenstand der 7-Segmentanzeige nach jeder Umlegung ausgegeben. Schließlich kann man den flag -l verwenden, für den Latex-Code mit dem Ergebnis des Programms. Dies ist hilfreich, da dafür gesorgt wird, dass kein Seitenumbruch mitten in einem ASCII-Art auftritt. \subsection{Implementierungsart} Die Implementierung ist in zwei Dateien aufgeteilt. In der Datei lib.rs wird die größtmögliche Hex-Zahl ermittelt und die Umlegungen werden entsprechend generiert. In der Datei main.rs werden die Nutzereingaben verarbeitet und das Ergebnis sowie weitere Informationen ausgegeben. Kommen wir zum Code in der Datei main.rs: Zuerst werden in der main-Funktion die Kommandozeilenargumente verarbeitet. Danach wird die Funktion \mintinline{rs}|hexmax::largest_hex| aufgerufen. Diese ermittelt die größtmögliche Hex-Zahl mit der gegebenenen maximalen Anzahl der Umlegungen. Danach wird die Hex-Zahl mit der Funktion \mintinline{rs}|hexmax::to_hex_str| zu einem String konvertiert und ausgegeben. Falls die Zwischenstände der Umlegungen ausgegeben werden sollen, wird die Funktion \mintinline{rs}|hexmax::gen_swaps| aufgerufen, die die Umlegungen zurückgibt. Anschließend wird jede Umlegung hintereinander ausgeführt und die 7-Segmentanzeige nach jeder Umlegung mithilfe der Funktion \mintinline{rs}|swap_print| als ASCII-Art ausgegeben. In der Datei lib.rs gibt ein konstantes Array namens \mintinline{rs}|HEX|, dass am Index der jeweiligen Ziffer eine Bitmap der 7-Segmentrepräsentation enthält. Bei 1 leuchtet das Segment und bei 0 nicht. Die Funktion \mintinline{rs}|change_digits| durchläuft Ziffer für Ziffer eines als Argument übergebenen Iterators. Für jede Ziffer wird nun jede mögliche Änderung der Ziffer durchgegangen, angefangen mit F,E,... . Dabei wird nach folgender Vorgehensweise berechnet, wie viele Stäbchen im Vergleich zur ursprünglichen Ziffer hingelegt und weggenommenen werden müssen: Sei $u$ die 7-Segmentbitmap der ursprünglichen Ziffer und $n$ die der neuen Ziffer: \begin{align} c = u \oplus n \\ h = c \& n \label{h} \\ g = c \& u \label{g} \end{align} Mit diesen Formeln erhält man eine Bitmap der hingelegten Stäbchen $h$ und der weggenommenen Stäbchen $g$. Zählt man nun die Einsen der binären Schreibweise von $h$ oder $g$, so ergibt sich die Anzahl der hingelegten bzw. weggenommenen Stäbchen. Mit diesen sowie der Gesamtzahl der hingelegten bzw. weggenommenen Stäbchen wird eine der Funktion \mintinline{rs}|change_digits| übergebene Lambdafunktion aufgerufen, sofern bei der Ziffernänderung nicht die maximale Anzahl der Umlegungen überschritten werden würde. Diese entscheidet mit diesen Angaben, ob die Ziffer entsprechend geändert werden soll. Diese Änderung ist entweder final oder es wird probiert, die Ziffer auf einen niedrigen Wert zu ändern. In der Funktion \mintinline{rs}|largest_hex| wird zuerst die Eingabedatei ausgelesen. Im Vector \mintinline{rs}|digits| werden die einzelnen Ziffern der gegebenen Hex-Zahl gespeichert. Innerhalb einer for-Schleife wird der Algorithmus wiederholt, bis die Anzahl der maximalen Umlegungen ausgeschöpft ist oder er schon so oft wiederholt wurde wie die Anzahl der Ziffern. Letzteres bedeutet nämlich, dass keine größere Zahl mehr gefunden werden kann, die alle Umlegungen ausschöpft. Zuerst wird probiert die vorderen Ziffern zu erhöhen. Dafür wird die Funktion \mintinline{rs}|change_digits| mit einer Lambdafunktion aufgerufen, die gleich die erste Ziffernänderung final akzeptiert. Die erste ist dabei automatisch die größtmögliche Erhöhung. Wenn die Anzahl der hingelegten ungleich der weggenommenen Stäbchen ist, müssen noch Stäbchen genommen bzw. hingelegt werden. Dafür wird Ziffer für Ziffer von hinten mithilfe der Funktion \mintinline{rs}|change_digits| betrachtet. Jede Ziffer wird nun so geändert, dass der Abstand zwischen den hingelegten und weggenommenen Stäbchen am geringsten ist. Nachdem alle Ziffern durchgegangen worden sind, ist die Anzahl der hingelegten gleich der weggenommenen Stäbchen. Allerdings kann es sein, dass dafür eine vorherige Änderung rückgängig gemacht werden musste und die maximale Anzahl der Umlegungen nicht ausgeschöpft wurde. In diesem Fall wird der Algorithmus nochmal wiederholt. Die Funktion \mintinline{rs}|gen_swaps| generiert aus den Änderungen der Ziffern konkrete Umlegungen. Dafür wird in einer for-Schleife über jede Ziffernänderung iteriert. Wie in \eqref{h} und \eqref{g} beschrieben, wird ermittelt welche Stäbchen dafür hingelegt und weggenommenen werden müssen. Die Bitmaps $h$ und $g$ werden Bit für Bit durchlaufen. Für jede 1, d. h. das Stäbchen wurde geändert, wird diese Änderung dem Vector \mintinline{rs}|moves_put| angehängt, wenn das Stäbchen hingelegt wurde. Wenn es weggenommenen wurde, dann wird sie dem Vector \mintinline{rs}|moves_taken| angehängt. Die beiden Vectors\footnote{Da Vector der Name der Struktur ist, habe ich hier auch die englische Mehrzahl verwendet. Eigentlich müsste es heißen \enquote{Variablen der Struktur Vector}} werden schließlich zurückgegeben. Ordnet man die Elemente der Vectors mit dem gleichen Index einander zu, so erhält man die Umlegungen. Die Funktion \mintinline{rs}|to_hex_str| konvertiert eine Slice mit den Ziffern zu einem hexadezimalen String. Dies wird erreicht, indem mit einer for-Schleife über alle Ziffern iteriert wird, und jede Ziffer einzeln zu dem entsprechenden Zeichen konvertiert wird. \section{Beispiele} \subsection{gegebenene Beispiele} \beispiel{0} Die Zahl D24 wird erst mit 3 weggenommenen und 2 hingelegten Stäbchen auf F24 erhöht. Danach kann man allerdings das übrige Stäbchen nicht bei den Ziffern 4 oder 2 loswerden, sodass die Zahl auf E24 geändert wird, da dort gleich viele Stäbchen hingelegt wie weggenommenen wurden. Darauffolgend wird wieder probiert die Ziffern zu erhöhen, wobei die erste Ziffer ausgelassen wird, sodass der Algorithmus schließlich zur Lösung EE4 kommt. \beispiel{1} \beispiel{2} \newcounter{beispielcounter} \forloop{beispielcounter}{3}{\value{beispielcounter}<6}{ hexmax\arabic{beispielcounter}.txt: \inputminted[breaklines,breakanywhere]{text}{ergebnisdateien/\arabic{beispielcounter}.txt} } \subsection{Negativbeispiel\label{negativ}} \begin{minted}[]{text} $ hexmax testdaten/negativBeispiel.txt ED8 \end{minted} Die richtige Lösung wäre F89. \section{Quellcode} src/lib.rs: \inputminted[breaklines,fontsize=\footnotesize]{rs}{src/lib.rs} \end{document}