diff --git a/planner/src/bin/weekly.rs b/planner/src/bin/weekly.rs index 3874ffc..0ffea8f 100644 --- a/planner/src/bin/weekly.rs +++ b/planner/src/bin/weekly.rs @@ -1,44 +1,2 @@ //! The weekly menu planner //! - -use cookbook::{Meal, fetch_meals}; -use planner::solver::{Variables, Domain, solve_all}; - - -fn generate_weekly_menu() -> String { - let assignments: Variables = [ - ("LundiMidi".to_string(), None), ("LundiSoir".to_string(), None), - ("MardiMidi".to_string(), None), ("MardiSoir".to_string(), None), - ("MercrediMidi".to_string(), None), ("MercrediSoir".to_string(), None), - ].iter().cloned().collect(); - let meals: Domain = Domain::new(fetch_meals()); - let validator = |vars: &Variables| { - let mut result = true; - for day in ["Lundi", "Mardi", "Mercredi"].into_iter() { - let all_day = vars.keys().filter(|k| k.starts_with(day)); - let mut nutri_value = 0; - for key in all_day { - nutri_value += vars.get(key) - .expect("no value here !") - .expect("no meal there !") - .nutritional_value() - } - println!("{} -> {}", day, nutri_value); - if nutri_value != 1200 { result = false; }; - } - println!("Validator returns {}", result); - result - }; - - let solutions = solve_all(assignments, &meals, validator); - format!("{:#?}", solutions) -} - -fn main() { - println!("{}", generate_weekly_menu()); -} - -#[cfg(test)] -mod tests { - -} diff --git a/planner/src/solver.rs b/planner/src/solver.rs index 54f7346..52aafc2 100644 --- a/planner/src/solver.rs +++ b/planner/src/solver.rs @@ -32,65 +32,115 @@ impl fmt::Debug for Domain { } } +type Constraint<'a,V> = fn(&Variables<'a,V>) -> bool; -/// Returns all possible Updates for next assignements, prepended with -/// a Clear to ensure the variable is unset before when leaving the branch. -fn assign_next<'a,'b, V>(assign: &'b Variables<'a, V>, domain: &'a Domain) - -> Option>> - where V: fmt::Debug -{ - // Panics on empty domain - // If domain values are filtered, then the branch is a dead end - if domain.values.is_empty() { panic!("No values in domain : {:?}", domain); }; +pub struct Problem<'a, V> { + /// The initial assignements map + variables: Variables<'a, V>, + /// Each variable has its associated domain + domains: HashMap>, + /// Set of constraints to validate + constraints: Vec>, +} - // TODO: should be able to inject a choosing strategy - if let Some((key,_)) = assign.iter().find(|(_, val)| val.is_none()) { - let mut updates = vec![Assignment::Clear(key.clone())]; - // TODO: should be able to filter domain values (inference, pertinence) - for value in domain.values.iter() { - updates.push(Assignment::Update(key.clone(), value)); +impl<'a,V> Problem<'a, V> { + + pub fn build() -> ProblemBuilder<'a,V> { + ProblemBuilder::new() + } + + /// Returns all possible Updates for next assignements, prepended with + /// a Clear to ensure the variable is unset before when leaving the branch. + fn _assign_next(&self) -> Option>> { + // TODO: should be able to inject a choosing strategy + if let Some((key,_)) = self.variables.iter().find(|(_, val)| val.is_none()) { + let domain = self.domains.get(key).expect("No domain for variable !"); + let mut updates = vec![Assignment::Clear(key.clone())]; + + if domain.values.is_empty() { panic!("No value in domain !"); } + // TODO: should be able to filter domain values (inference, pertinence) + for value in domain.values.iter() { + updates.push(Assignment::Update(key.clone(), value)); + } + Some(updates) + } else { // End of assignements + None } - Some(updates) - } else { // End of assignements - None + } + + /// Checks that the current assignments doesn't violate any constraint + fn _is_valid(&self) -> bool { + for validator in self.constraints.iter() { + if validator(&self.variables) == false { return false; } + } + return true; + } + + /// Visit all possible solutions, using a stack. + pub fn solve_all(&mut self) -> Vec> + where V: Clone + fmt::Debug + { + let mut solutions: Vec> = vec![]; + let mut stack: Vec> = vec![]; + stack.append(&mut self._assign_next().unwrap()); + loop { + let node = stack.pop(); + if node.is_none() { break; }; + match node.unwrap() { + Assignment::Update(key, val) => { + // Assign the variable and open new branches, if any. + *self.variables.get_mut(&key).unwrap() = Some(val); + // TODO: handle case of empty domain.values + if let Some(mut nodes) = self._assign_next() { + stack.append(&mut nodes); + } else { + // Assignements are completed + if self._is_valid() { + solutions.push(self.variables.clone()); + }; + }; + }, + Assignment::Clear(key) => { + // We are closing this branch, unset the variable + *self.variables.get_mut(&key).unwrap() = None; + }, + }; + }; + solutions } } -/// Visit all possible solutions, using a stack. -pub fn solve_all<'a, V>( - mut assign: Variables<'a, V>, - domain: &'a Domain, - is_valid: fn(&Variables<'a,V>) -> bool - ) -> Vec> - where V: Clone + fmt::Debug -{ - let mut solutions: Vec> = vec![]; - let mut stack: Vec> = vec![]; - stack.append(&mut assign_next(&assign,domain).unwrap()); - loop { - let node = stack.pop(); - if node.is_none() { break; }; - match node.unwrap() { - Assignment::Update(key, val) => { - // Assign the variable and open new branches, if any. - *assign.get_mut(&key).unwrap() = Some(val); - // TODO: handle case of empty domain.values - if let Some(mut nodes) = assign_next(&assign, domain) { - stack.append(&mut nodes); - } else { - // Assignements are completed - if is_valid(&assign) { - solutions.push(assign.clone()); - }; - }; - }, - Assignment::Clear(key) => { - // We are closing this branch, unset the variable - *assign.get_mut(&key).unwrap() = None; - }, - }; - }; - solutions +pub struct ProblemBuilder<'a, V>(Problem<'a, V>); + +impl<'a, V> ProblemBuilder<'a, V> { + fn new() -> ProblemBuilder<'a, V> { + ProblemBuilder( + Problem{ + variables: Variables::new(), + domains: HashMap::new(), + constraints: Vec::new(), + }) + } + + pub fn add_variable( + mut self, + name: String, + domain: &'a Domain, + value: Option<&'a V>, + ) -> Self { + self.0.variables.insert(name.clone(), value); + self.0.domains.insert(name, domain); + self + } + + pub fn add_constraint(mut self, cons: Constraint<'a,V>) -> Self { + self.0.constraints.push(cons); + self + } + + pub fn finish(self) -> Problem<'a, V> { + self.0 + } } @@ -99,41 +149,41 @@ mod tests { #[test] fn test_solver_find_pairs() { use super::*; - // Find all pairs of two differents - let assign: Variables = [ - ("Left".to_string(), None), - ("Right".to_string(), None), - ].iter().cloned().collect(); let domain = Domain::new(vec![1,2,3]); - let constraint = |assign: &Variables| { - assign.get("Left").unwrap() == assign.get("Right").unwrap() - }; + let mut problem: Problem<_> = Problem::build() + .add_variable(String::from("Left"), &domain, None) + .add_variable(String::from("Right"), &domain, None) + .add_constraint( + |assign: &Variables| { + assign.get("Left").unwrap() == assign.get("Right").unwrap() + }) + .finish(); + let solutions: Vec> = vec![ [("Left".to_string(), Some(&3)), ("Right".to_string(), Some(&3)),].iter().cloned().collect(), [("Left".to_string(), Some(&2)), ("Right".to_string(), Some(&2)),].iter().cloned().collect(), [("Left".to_string(), Some(&1)), ("Right".to_string(), Some(&1)),].iter().cloned().collect(), ]; - assert_eq!(solve_all(assign, &domain, constraint), solutions); + assert_eq!(problem.solve_all(), solutions); } #[test] fn test_solver_find_pairs_with_initial() { use super::*; - // Find all pairs of two differents - let assign: Variables = [ - ("Left".to_string(), None), - ("Right".to_string(), Some(&2)), - ].iter().cloned().collect(); let domain = Domain::new(vec![1,2,3]); - let constraint = |assign: &Variables| { - assign.get("Left").unwrap() == assign.get("Right").unwrap() - }; + let mut problem: Problem<_> = Problem::build() + .add_variable("Left".to_string(), &domain, None) + .add_variable("Right".to_string(), &domain, Some(&2)) + .add_constraint( |assign: &Variables| { + assign.get("Left").unwrap() == assign.get("Right").unwrap() + }) + .finish(); + let solutions: Vec> = vec![ [("Left".to_string(), Some(&2)), ("Right".to_string(), Some(&2)),].iter().cloned().collect(), ]; - assert_eq!(solve_all(assign, &domain, constraint), solutions); + assert_eq!(problem.solve_all(), solutions); } } -