#![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, } struct PartOperation { // mögliche Zwischenergebnisse eines Teils der Rechnung, der nur Punktrechnung enthält. results: HashMap>, last_operator: Option, // der Operator, der vor dieser Rechnungsteil steht } // Struktur, die eindeutige Ergebnisse speichert struct ResultStore { riddles: HashMap, // speichert eindeutige Rätsel // speichert Ergebnisse, für die es bereits mehrere Operatorenkombinationen gibt results_already_taken: HashSet, } impl ResultStore { // 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) { // 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 = 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 = 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::(args.solution_print, &digits) } else {calc_results::(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 (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> = 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> = 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 = 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, results: &mut HashMap>, 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>, results: &mut ResultStore, part_result: T, operators: Option) { 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 { if index == 0 {None} else {Some(index as usize - 1)} } // Hilfsfunktion, um eine einzelne Ziffer als Zwischenergebnis hinzufügen fn insert_digit(digits: &[u8], i: usize) -> PartOperation { let mut result_map: HashMap> = 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(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); }