diff --git a/cookbook/src/lib.rs b/cookbook/src/lib.rs index 26ef8ac..ee28eb0 100644 --- a/cookbook/src/lib.rs +++ b/cookbook/src/lib.rs @@ -57,6 +57,7 @@ pub mod ingredients { use super::{SqliteConnection, schema}; use super::diesel::prelude::*; + /// A wrapper of [`IngredientList`] with DB connection capacity. pub struct IngredientListManager<'a>(&'a SqliteConnection, IngredientList); impl<'a> IngredientListManager<'a> { @@ -98,12 +99,9 @@ pub mod ingredients { fn find(conn: &SqliteConnection, name: &str) -> Option { use self::schema::ingredients::dsl::*; - match ingredients.filter(alias.like(name)) - .first(conn) - { - Ok(ingdt) => Some(ingdt), - Err(_) => None, - } + ingredients.filter(alias.like(name)) + .first(conn) + .ok() } fn create(conn: &SqliteConnection, name: &str) -> Result { diff --git a/cookbook/src/models.rs b/cookbook/src/models.rs index 6111a64..0b10343 100644 --- a/cookbook/src/models.rs +++ b/cookbook/src/models.rs @@ -144,6 +144,12 @@ pub struct Recipe { pub preparation: String, } +impl PartialEq for Recipe { + fn eq(&self, other: &Recipe) -> bool { + self.id == other.id + } +} + #[derive(Insertable, Debug)] #[table_name="recipes"] pub struct NewRecipe<'a> { diff --git a/planner/src/constraint.rs b/planner/src/constraint.rs new file mode 100644 index 0000000..70a8585 --- /dev/null +++ b/planner/src/constraint.rs @@ -0,0 +1,119 @@ +//! Constraints building +//! +//! # Ideas +//! +//! Each constraints will be updated on every assignment, +//! thus their status is always inspectable. +//! A constraint applies to a set of variables, identified +//! by a key of type `K`. +//! A constraint owns references to actual values assigned, +//! used to perform checks. + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Status { + Validated,// Solution is valid + Unknown, // Constraint cannot resolve yet (unbound variables) + Violated, // Solution is invalid +} + +use std::collections::HashMap; + +// *Like this +enum ValueChecker { + AllDifferent, + AllSame, +} + +pub struct Constraint<'c, V, K> { + status: Status, + variables: HashMap<&'c K, Option<&'c V>>, + // TODO: add a ValueChecker Trait object, + // or just a simple Enum.* + // to provide the check_status procedure, given + // a vector to variables values. +} + +impl<'c, V, K> Constraint<'c, V, K> + where K: Eq + std::hash::Hash, + V: PartialEq, +{ + + pub fn new(vars: Vec<&'c K>) -> Self { + let len = vars.len(); + Self { + status: Status::Unknown, + variables: vars.into_iter() + .zip(vec![None; len]) + .collect(), + } + } + + pub fn status(&self) -> &Status { + &self.status + } + + fn check_status(vars: Vec<&Option<&V>>) -> Status { + /// LEts do an hacky NotEqualConstraint + let vars_len = vars.len(); + let set_vars: Vec<&Option<&V>> = vars.into_iter().filter(|v| v.is_some()).collect(); + let is_complete = vars_len == set_vars.len(); + for (idx, value) in set_vars.iter().enumerate() { + let violated = set_vars.iter() + .enumerate() + .filter(|(i,_)| *i != idx) + .fold(false, |res, (_,v)| { + res || v == value + }); + if violated { return Status::Violated; } + } + match is_complete { + true => Status::Validated, + false => Status::Unknown, + } + } + + fn update_status(&mut self) { + self.status = Self::check_status(self.variables.values().collect()); + } + + pub fn update(&mut self, key: &K, new_value: Option<&'c V>) { + if let Some(value) = self.variables.get_mut(key) { + // Actually update values + dbg!(*value = new_value); + self.update_status(); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_all_different_problem() { + use crate::solver::{Domain, Problem}; + use super::Constraint; + + let domain = Domain::new(vec![1, 2, 3]); + let mut problem = Problem::build() + .add_variable("Left", domain.all(), None) + .add_variable("Right", domain.all(), None) + .add_constraint(Constraint::new(vec![&"Left", &"Right"])) + .finish(); + + let solutions = vec![ + (("Left", Some(&1)), ("Right", Some(&2))), + (("Left", Some(&1)), ("Right", Some(&3))), + (("Left", Some(&2)), ("Right", Some(&1))), + (("Left", Some(&2)), ("Right", Some(&3))), + (("Left", Some(&3)), ("Right", Some(&1))), + (("Left", Some(&3)), ("Right", Some(&2))), + ]; + let results = problem.solve_all(); + println!("{:#?}", results); + assert!(results.len() == solutions.len()); + results.into_iter().for_each(|res| { + let res = (("Left", *res.get("Left").unwrap()), + ("Right", *res.get("Right").unwrap())) ; + assert!(solutions.contains(&res)); + }); + } +} diff --git a/planner/src/lib.rs b/planner/src/lib.rs index 90ca004..746523c 100644 --- a/planner/src/lib.rs +++ b/planner/src/lib.rs @@ -3,7 +3,7 @@ use cookbook::recipes::Recipe; pub mod solver; pub mod template; - +pub mod constraint; pub use solver::{Domain, DomainValues}; /// We mainly use Recipe as the domain value type diff --git a/planner/src/solver.rs b/planner/src/solver.rs index 58eecf2..d72aa1c 100644 --- a/planner/src/solver.rs +++ b/planner/src/solver.rs @@ -6,11 +6,16 @@ use std::hash::Hash; use std::clone::Clone; use std::collections::HashMap; -/// An assignments map of variables -type Variables<'a, V> = Vec>; +use crate::constraint::{Status, Constraint}; + /// A solution returned by [`Solver`] pub type Solution<'a, V, K> = HashMap>; + +/// An assignments map of variables +type Variables<'a, V> = Vec>; + +/// Orders used by solver to update variables enum Assignment<'a, V> { Update(usize, &'a V), Clear(usize) @@ -101,7 +106,7 @@ impl fmt::Debug for Domain { } -pub type Constraint<'a,V> = fn(&Variables<'a,V>) -> bool; +//pub type Constraint<'a,V> = fn(&Variables<'a,V>) -> bool; /// Could be more efficient to just use fixed array of options as variables, @@ -109,23 +114,23 @@ pub type Constraint<'a,V> = fn(&Variables<'a,V>) -> bool; /// Domains could be a similar array of DomainValues. /// It makes sense with an array where indexing is O(1) -pub struct Problem<'a, V, K> { +pub struct Problem<'p, V, K> { keys: Vec, /// The initial assignements map - variables: Variables<'a, V>, + variables: Variables<'p, V>, /// Each variable has its associated domain - domains: Vec>, + domains: Vec>, /// Set of constraints to validate - constraints: Vec>, + constraints: Vec>, } -impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { +impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> { - pub fn build() -> ProblemBuilder<'a,V, K> { + pub fn build() -> ProblemBuilder<'p,V, K> { ProblemBuilder::new() } - pub fn from_template() -> Problem<'a, V, K> { + pub fn from_template() -> Problem<'p, V, K> { let builder = Self::build(); builder.finish() @@ -133,7 +138,7 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { /// Returns all possible Updates for next assignements, prepended with /// a Clear to ensure the variable is unset before when leaving the branch. - fn _push_updates(&self) -> Option>> { + fn _push_updates(&self) -> Option>> { // TODO: should be able to inject a choosing strategy if let Some(key) = self._next_assign() { let domain_values = self.domains @@ -165,15 +170,19 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { /// 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; } + for status in self.constraints.iter().map(|c| c.status()) { + if status == &Status::Violated { return false; } } return true; } - fn _solve(&mut self, limit: Option) -> Vec> { + fn _get_key(&self, idx: usize) -> &K { + &self.keys[idx] + } + + fn _solve(&mut self, limit: Option) -> Vec> { let mut solutions: Vec> = vec![]; - let mut stack: Vec> = vec![]; + let mut stack: Vec> = vec![]; if let Some(mut init_updates) = self._push_updates() { stack.append(&mut init_updates); } else { @@ -196,6 +205,12 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { Assignment::Update(idx, val) => { // Assign the variable and open new branches, if any. self.variables[idx] = Some(val); + { + let v_key = &self.keys[idx]; + self.constraints.iter_mut().for_each(|cons| { + cons.update(&v_key, Some(val)); + }); + } // TODO: handle case of empty domain.values if let Some(mut nodes) = self._push_updates() { stack.append(&mut nodes); @@ -214,6 +229,10 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { Assignment::Clear(idx) => { // We are closing this branch, unset the variable self.variables[idx] = None; + let v_key = &self.keys[idx]; + self.constraints.iter_mut().for_each(|cons| { + cons.update(&v_key, None); + }); }, }; }; @@ -221,14 +240,14 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { } /// Returns all complete solutions, after visiting all possible outcomes using a stack (DFS). - pub fn solve_all(&mut self) -> Vec> + pub fn solve_all(mut self) -> Vec> where V: fmt::Debug, K: fmt::Debug, { self._solve(None) // No limit } - pub fn solve_one(&mut self) -> Option> + pub fn solve_one(mut self) -> Option> where V: fmt::Debug, K: fmt::Debug, { @@ -236,10 +255,10 @@ impl<'a, V, K: Eq + Hash + Clone> Problem<'a, V, K> { } } -pub struct ProblemBuilder<'a, V, K>(Problem<'a, V, K>); +pub struct ProblemBuilder<'p, V, K>(Problem<'p, V, K>); -impl<'a, V, K: Eq + Hash + Clone> ProblemBuilder<'a, V, K> { - fn new() -> ProblemBuilder<'a, V, K> { +impl<'p, V, K: Eq + Hash + Clone> ProblemBuilder<'p, V, K> { + fn new() -> ProblemBuilder<'p, V, K> { ProblemBuilder( Problem{ keys: Vec::new(), @@ -249,7 +268,7 @@ impl<'a, V, K: Eq + Hash + Clone> ProblemBuilder<'a, V, K> { }) } - pub fn add_variable(mut self, name: K, domain: Vec<&'a V>, value: Option<&'a V>) -> Self + pub fn add_variable(mut self, name: K, domain: Vec<&'p V>, value: Option<&'p V>) -> Self { self.0.keys.push(name); self.0.variables.push(value); @@ -257,12 +276,12 @@ impl<'a, V, K: Eq + Hash + Clone> ProblemBuilder<'a, V, K> { self } - pub fn add_constraint(mut self, cons: Constraint<'a,V>) -> Self { + pub fn add_constraint(mut self, cons: Constraint<'p,V,K>) -> Self { self.0.constraints.push(cons); self } - pub fn finish(self) -> Problem<'a, V, K> { + pub fn finish(self) -> Problem<'p,V, K> { self.0 } } @@ -277,9 +296,6 @@ mod tests { let mut problem: Problem<_, _> = Problem::build() .add_variable(String::from("Left"), domain.all(), None) .add_variable(String::from("Right"), domain.all(), None) - .add_constraint(|assign: &Variables| { - assign[0] == assign[1] - }) .finish(); let solutions: Vec> = vec![ @@ -298,9 +314,6 @@ mod tests { let mut problem: Problem<_, _> = Problem::build() .add_variable("Left".to_string(), domain.all(), None) .add_variable("Right".to_string(), domain.all(), Some(&2)) - .add_constraint( |assign: &Variables| { - assign[0] == assign[1] - }) .finish(); let solutions: Vec> = vec![ diff --git a/web/src/main.rs b/web/src/main.rs index 99c548f..2810497 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -100,9 +100,8 @@ mod api { for (var, dom, ini) in template::Template::generate_variables(&domain) { problem = problem.add_variable(var, dom, ini); } - let mut problem = problem - .add_constraint(|_| true) - .finish(); + let problem = problem.finish(); + if let Some(one_result) = problem.solve_one() { Json(TemplateObject { items: one_result @@ -158,9 +157,8 @@ mod api { // If found, override initial value problem = problem.add_variable(var, dom, ini); }; - let mut problem = problem - .add_constraint(|_| true) - .finish(); + let problem = problem.finish(); + if let Some(one_result) = problem.solve_one() { Json(TemplateObject { items: one_result