From 1a78cec6325a3e37f1b56bb356939f78fec5437c Mon Sep 17 00:00:00 2001 From: MrGeorgen Date: Sat, 5 Feb 2022 19:00:27 +0100 Subject: [PATCH] basic a2 --- .gitignore | 8 +- Aufgabe2-Rechenrätsel/Cargo.toml | 13 ++ Aufgabe2-Rechenrätsel/doc.tex | 119 ++++++++++++++++ Aufgabe2-Rechenrätsel/src/main.rs | 222 ++++++++++++++++++++++++++++++ 4 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 Aufgabe2-Rechenrätsel/Cargo.toml create mode 100644 Aufgabe2-Rechenrätsel/doc.tex create mode 100644 Aufgabe2-Rechenrätsel/src/main.rs diff --git a/.gitignore b/.gitignore index 5c4f269..c3870e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # ---> Rust # Generated by Cargo # will have compiled files and executables -/target/ +target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -11,3 +11,9 @@ Cargo.lock **/*.rs.bk beispieldaten/ + +# LaTex +*.aux +*.log +*.pdf +*.toc diff --git a/Aufgabe2-Rechenrätsel/Cargo.toml b/Aufgabe2-Rechenrätsel/Cargo.toml new file mode 100644 index 0000000..cc2b897 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rechenrätsel" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "3.0.10", features = ["derive"] } +num-derive = "0.3.3" +num-integer = "0.1.44" +num-traits = "0.2.14" +rand = "0.8.4" diff --git a/Aufgabe2-Rechenrätsel/doc.tex b/Aufgabe2-Rechenrätsel/doc.tex new file mode 100644 index 0000000..0eaedb6 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/doc.tex @@ -0,0 +1,119 @@ +\documentclass[a4paper,10pt,ngerman]{scrartcl} +\usepackage{babel} +\usepackage[T1]{fontenc} +\usepackage[utf8x]{inputenc} +\usepackage[a4paper,margin=2.5cm,footskip=0.5cm]{geometry} + +% Die nächsten drei Felder bitte anpassen: +\newcommand{\Aufgabe}{Aufgabe 2: Rechenrätsel} % 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{listings} +\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} + +% Diese beiden Pakete müssen zuletzt geladen werden +%\usepackage{hyperref} % Anklickbare Links im Dokument +\usepackage{cleveref} + +% 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} +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: +\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 + \end{align} + \item Bei $n$ Ziffern muss für $m_i$, die Anzahl, mit der jede der neun + Ziffern vorkommt, gelten: + \begin{align} + \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. + +\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. + +\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! + +\section{Quellcode} +Unwichtige Teile des Programms sollen hier nicht abgedruckt werden. Dieser Teil sollte nicht mehr als 2–3 Seiten umfassen, maximal 10. + + + +\end{document} diff --git a/Aufgabe2-Rechenrätsel/src/main.rs b/Aufgabe2-Rechenrätsel/src/main.rs new file mode 100644 index 0000000..1c28895 --- /dev/null +++ b/Aufgabe2-Rechenrätsel/src/main.rs @@ -0,0 +1,222 @@ +use clap::Parser; +use std::collections::HashSet; +use std::collections::HashMap; +use rand::distributions::{Distribution, Uniform}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +#[derive(FromPrimitive)] +enum Operator { + Add, + Subtract, + Multiplicate, + Divide, +} + +#[derive(FromPrimitive, PartialEq, Clone, Copy)] +enum AddSubOrMultiplicateDivide { + AddSub, + MultiplicateDivide, +} + +#[derive(Parser)] +#[clap(author, version, 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, + + /// zeigt die Lösung an + #[clap(short, long, takes_value = false)] + solution_print: bool, +} + +type RiddleMap = HashMap; + +struct PartOperation { + results: RiddleMap, + last_operator: Option, +} + +struct ResultStore { + riddles: RiddleMap, + results_already_taken: HashSet, +} + +impl ResultStore { + fn new() -> Self { + Self{ + riddles: HashMap::new(), + results_already_taken: HashSet::new(), + } + } + fn store(&mut self, result: i64, operators: u64) { + if self.results_already_taken.contains(&result) {return;} + match self.riddles.remove(&result) { + None => {self.riddles.insert(result, operators);} + Some(_x) => {self.results_already_taken.insert(result);} + } + } +} + + +fn main() { + 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; + } + } + { + let mut results = ResultStore::new(); + for dm_as_map in 0..2u64.pow(args.count as u32) { + let mut results_multiplicate: Vec = Vec::new(); + { + let mut last_i: usize = 0; + let mut i: usize; + let mut state: AddSubOrMultiplicateDivide = FromPrimitive::from_u64(dm_as_map & 1).unwrap(); + while last_i < args.count as usize { + i = last_i; + while i < args.count as usize && dm_as_map >> i & 1 == state as u64 { + if state == AddSubOrMultiplicateDivide::AddSub { + results_multiplicate.push(insert_digit(&digits, i)); + } + i += 1; + } + if state == AddSubOrMultiplicateDivide::MultiplicateDivide { + let mut part_results = ResultStore::new(); + i += 1; + let mut digits_calc = digits[last_i .. i].iter(); + let first_digit = *digits_calc.next().unwrap() as i64; + calc_part(digits_calc, &mut part_results, last_i as u8 * 2, first_digit, 0); + results_multiplicate.push(PartOperation{ + results: part_results.riddles, + last_operator: last_operator_helper(last_i), + }); + } + 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 u64 { + results_multiplicate.push(insert_digit(&digits, digits.len() - 1)); + } + { + 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 not_first = false; + for (result, operators) in results.riddles { + if not_first {println!();} + not_first = true; + print_riddle(&digits, false, result, operators); + if args.solution_print { + print_riddle(&digits, true, result, operators); + } + } + } +} + +fn calc_part<'a>(mut iter: impl Clone + Iterator, results: &mut ResultStore, operator_index: u8, part_result: i64, operators: u64) { + match iter.next() { + None => results.store(part_result, operators), + Some(next) => { + let next_digit = *next as i64; + for operator in [Operator::Multiplicate, Operator::Divide] { + calc_part(iter.clone(), results, operator_index + 2, + match operator { + Operator::Multiplicate => part_result * next_digit, + Operator::Divide => { + if part_result % next_digit != 0 {continue;} + part_result / next_digit + } + _ => 0, + }, + operators | (operator as u64) << operator_index); + } + } + } +} + +fn add_sub<'a>(mut iter: impl Clone + Iterator, results: &mut ResultStore, part_result: i64, operators: u64) { + match iter.next() { + None => { + if part_result > 0 {results.store(part_result, operators);} + } + Some(next) => { + for (next_result, part_operators) in &next.results { + 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, + _ => 0, + }, + operators | part_operators | (operator as u64) << 2 * next.last_operator.unwrap() + ); + } + } + } + } +} + +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 { + let mut result_map = RiddleMap::new(); + result_map.insert(digits[i] as i64, 0); + return PartOperation{ + results: result_map, + last_operator: last_operator_helper(i), + }; +} + +fn print_riddle(digits: &Vec, show_solution: bool, result: i64, operators: u64) { + print!("{}: ", if show_solution {"Lösung"} else {"Rätsel"}); + let mut i = 0; + for digit in digits[.. digits.len() - 1].iter() { + print!("{} {} ", digit, if show_solution { + match FromPrimitive::from_u64(operators >> i & 3).unwrap() { + Operator::Multiplicate => '*', + Operator::Divide => ':', + Operator::Add => '+', + Operator::Subtract => '-', + } + } else {'○'}); + i += 2; + } + println!("{} = {}", digits.last().unwrap(), result); +}