diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index b6df8b3..44fd388 100644 --- a/src/expressions/expression.rs +++ b/src/expressions/expression.rs @@ -25,10 +25,6 @@ impl Expression { } } - pub fn is_not(&self) -> bool { - matches!(self, Expression::Not(_)) - } - pub fn exists(&self, atomic_value: &str) -> bool { match self { Expression::Not(expr) => expr.exists(atomic_value), @@ -36,6 +32,26 @@ impl Expression { Expression::Atomic(value) => value == atomic_value, } } + + pub fn count_distinct(&self) -> usize { + self.count_distinct_with_visited(vec![].as_mut()) + } + + fn count_distinct_with_visited(&self, visited: &mut Vec) -> usize { + match self { + Expression::Not(expr) => expr.count_distinct_with_visited(visited), + Expression::Binary { left, right, .. } => + left.count_distinct_with_visited(visited) + right.count_distinct_with_visited(visited), + Expression::Atomic(value) => { + if visited.contains(value) { + 0 + } else { + visited.push(value.clone()); + 1 + } + } + } + } } impl OppositeEq for Expression { @@ -89,6 +105,30 @@ mod tests { use crate::{and, atomic, implies, not, or}; use crate::expressions::expression::Expression; + #[test] + fn test_count_distinct() { + let expression = and!( + atomic!("a"), + or!( + atomic!("b"), + atomic!("c") + ) + ); + assert_eq!(expression.count_distinct(), 3); + } + + #[test] + fn test_count_distinct_duplicates() { + let expression = and!( + atomic!("a"), + or!( + atomic!("b"), + atomic!("a") + ) + ); + assert_eq!(expression.count_distinct(), 2); + } + #[test] fn test_expression_a_and_not_b_display() { let expression = and!( diff --git a/src/expressions/mod.rs b/src/expressions/mod.rs index afe4cfc..82f6ad9 100644 --- a/src/expressions/mod.rs +++ b/src/expressions/mod.rs @@ -2,4 +2,5 @@ pub mod expression; pub mod operator; #[macro_use] pub mod helpers; -pub mod simplify; \ No newline at end of file +pub mod simplify; +mod truth_table; \ No newline at end of file diff --git a/src/expressions/truth_table.rs b/src/expressions/truth_table.rs new file mode 100644 index 0000000..f2a4371 --- /dev/null +++ b/src/expressions/truth_table.rs @@ -0,0 +1,154 @@ +use serde::{Deserialize, Serialize}; + +use crate::expressions::expression::Expression; +use crate::utils::array::Distinct; + +type TruthMatrix = Vec>; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TruthTable { + header: Vec, + truth_matrix: TruthMatrix, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Hide { + #[default] + None, + True, + False, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Sort { + #[default] + Default, + TrueFirst, + FalseFirst, +} + +#[derive(Debug, Default, Deserialize)] +pub struct TruthTableOptions { + pub sort: Sort, + pub hide: Hide, +} + +impl TruthTable { + pub fn new(expression: &Expression, options: TruthTableOptions) -> Self { + let header = Self::extract_header(expression); + let truth_matrix = Self::generate_truth_matrix(expression); + Self { header, truth_matrix } + } + + /// Extracts the header for the truth table from the expression + /// Duplicate values are removed. + /// - Arguments + /// - `expression` - The expression to extract the header from + /// - Returns + /// - A vector of strings representing the header + /// # Example + /// ``` + /// let expression = TruthTable::extract_header(&atomic!("A")); + /// let complex_expression = TruthTable::extract_header(&implies!(and!(atomic!("A"), atomic!("B")), or!(atomic!("C"), atomic!("D")))); + /// assert_eq!(expression, vec!["A"]); + /// assert_eq!(complex_expression, vec!["A", "B", "A ⋀ B", "C", "D", "(C ⋁ D)", "A ⋀ B ➔ (C ⋁ D)"]); + /// ``` + fn extract_header(expression: &Expression) -> Vec { + match expression { + not @ Expression::Not(expr) => { + let mut header = Self::extract_header(expr); + header.push(not.to_string()); + header.distinct(); + header + } + binary @ Expression::Binary { left, right, .. } => { + let mut header = Self::extract_header(left); + header.extend(Self::extract_header(right)); + header.push(binary.to_string()); + header.distinct(); + header + } + Expression::Atomic(value) => vec![value.clone()], + } + } + + fn generate_truth_matrix(expression: &Expression) -> TruthMatrix { + todo!() + } + + fn helper_matrix(number_of_atomics: usize) -> TruthMatrix { + todo!("Create a matrix with 2^number_of_atomics rows and number_of_atomics columns") + } + + fn resolve_expression(expression: &Expression, row: &[bool]) -> bool { + todo!("Resolve the expression with the given row of booleans") + } + + fn find_expression(expression: Expression, expressions: &[Expression]) -> Option { + todo!("Find the expression in the truth table and return index") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_atomic_expression() { + let expression = atomic!("A"); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A"]); + } + + #[test] + fn test_not_expression() { + let expression = not!(atomic!("A")); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "¬A"]); + } + + #[test] + fn test_binary_and_expression() { + let expression = and!(atomic!("A"), atomic!("B")); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "B", "A ⋀ B"]); + } + + #[test] + fn test_binary_or_expression() { + let expression = or!(atomic!("A"), atomic!("B")); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "B", "(A ⋁ B)"]); + } + + #[test] + fn test_binary_implies_expression() { + let expression = implies!(atomic!("A"), atomic!("B")); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "B", "A ➔ B"]); + } + + #[test] + fn test_complex_expression() { + let expression = implies!(and!(atomic!("A"), atomic!("B")), or!(atomic!("C"), atomic!("D"))); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "B", "A ⋀ B", "C", "D", "(C ⋁ D)", "A ⋀ B ➔ (C ⋁ D)"]); + } + + #[test] + fn test_equal_expressions_should_not_duplicate() { + let expression = and!(atomic!("A"), and!(atomic!("A"), and!(atomic!("A"), atomic!("A")))); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "A ⋀ A", "A ⋀ A ⋀ A", "A ⋀ A ⋀ A ⋀ A"]); + } + + #[test] + fn test_somewhat_equal() { + let expression = and!(atomic!("A"), and!(or!(not!(atomic!("A")), atomic!("B")), atomic!("A"))); + let header = TruthTable::extract_header(&expression); + assert_eq!(header, vec!["A", "¬A", "B", "(¬A ⋁ B)", "(¬A ⋁ B) ⋀ A", "A ⋀ (¬A ⋁ B) ⋀ A"]); + } +} diff --git a/src/main.rs b/src/main.rs index befb31d..00f3525 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod parsing; mod routing; mod language; mod config; +mod utils; #[tokio::main] async fn main() { diff --git a/src/routing/simplify.rs b/src/routing/simplify.rs index 1210500..375de19 100644 --- a/src/routing/simplify.rs +++ b/src/routing/simplify.rs @@ -42,13 +42,16 @@ struct SimplifyResponse { // TODO async fn simplify(Path(path): Path, query: Query, accept_language: Option) -> Response { - if let Ok(expression) = Expression::try_from(path.as_str()) { - let simplified = expression.simplify(); + if let Ok(mut expression) = Expression::try_from(path.as_str()) { + let before = expression.to_string(); + if query.simplify { + expression = expression.simplify(); + } Json(SimplifyResponse { - before: expression.to_string(), - after: simplified.to_string(), + before, + after: expression.to_string(), order_of_operations: vec![], // TODO - expression: simplified, + expression, }).into_response() } else { (StatusCode::BAD_REQUEST, "Invalid expression").into_response() diff --git a/src/utils/array.rs b/src/utils/array.rs new file mode 100644 index 0000000..fce5722 --- /dev/null +++ b/src/utils/array.rs @@ -0,0 +1,42 @@ +use std::ops::{Deref, DerefMut}; +#[macro_export] +macro_rules! set { + () => { std::collections::HashSet::new() }; + ($($x:expr),*) => { + { + let mut temp_set = std::collections::HashSet::new(); + $( + temp_set.insert($x); + )* + temp_set + } + }; +} + +pub trait Distinct { + fn distinct(&mut self); +} + +impl Distinct for Vec { + fn distinct(&mut self) { + *self = self.iter() + .fold(vec![], |mut acc, x| { + if !acc.contains(x) { + acc.push(x.clone()); + } + acc + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_distinct() { + let mut vec = vec![1, 2, 3, 1, 2, 3]; + vec.distinct(); + assert_eq!(vec, vec![1, 2, 3]); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..615880a --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod array; \ No newline at end of file