400 lines
18 KiB
Rust
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);
|
|
}
|