This commit is contained in:
2022-02-05 19:00:27 +01:00
parent f0fc466452
commit 1a78cec632
4 changed files with 361 additions and 1 deletions

8
.gitignore vendored
View File

@ -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

View File

@ -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"

View File

@ -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 23 Seiten umfassen, maximal 10.
\end{document}

View File

@ -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<i64, u64>;
struct PartOperation {
results: RiddleMap,
last_operator: Option<usize>,
}
struct ResultStore {
riddles: RiddleMap,
results_already_taken: HashSet<i64>,
}
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<u8> = vec!(0; (args.count + 1) as usize);
{
let mut selectable_digits: Vec<u8> = 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<u8> = 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<PartOperation> = 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<Item = &'a u8>, 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<Item = &'a PartOperation>, 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<usize> {
if index == 0 {None} else {Some(index as usize - 1)}
}
fn insert_digit(digits: &Vec<u8>, 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<u8>, 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);
}