Files
bwinf40-runde2/Aufgabe2-Rechenrätsel/src/main.rs
2022-04-25 13:08:42 +02:00

400 lines
18 KiB
Rust

#![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.
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<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),
}
}
// gibt das Rätsel bzw. die Lösung des Rätsels aus
fn print_riddle<T: Display>(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() {
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);
}