Ignore Case function

This commit is contained in:
Martin Berg Alstad 2024-06-21 18:07:42 +02:00
parent d9c0d90af4
commit e7f1ae156d
5 changed files with 135 additions and 49 deletions

View File

@ -123,3 +123,18 @@ GET {{url}}/simplify/{{expression}}
### GET with simplify="true" ### GET with simplify="true"
GET {{url}}/simplify/A?simplify=true&hide=NONE&sort=DEFAULT&caseSensitive=false&hideIntermediate=false GET {{url}}/simplify/A?simplify=true&hide=NONE&sort=DEFAULT&caseSensitive=false&hideIntermediate=false
### GET and ignore case
< {%
import {expression} from "./common";
expression("A & a")
%}
GET {{url}}/simplify/{{expression}}?ignoreCase=true
> {%
client.test("Response body is the same as the input", () => {
client.assert(response.body.after === "A", "Response body is not simplified to 'a'");
});
%}

View File

@ -7,10 +7,6 @@ use serde::{Deserialize, Serialize};
use crate::expressions::operator::BinaryOperator; use crate::expressions::operator::BinaryOperator;
use crate::parsing::expression_parser::parse_expression; use crate::parsing::expression_parser::parse_expression;
pub trait OppositeEq {
fn opposite_eq(&self, other: &Self) -> bool;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum Expression { pub enum Expression {
@ -39,14 +35,32 @@ impl Expression {
Expression::Atomic(value) => HashSet::from([value.clone()]) Expression::Atomic(value) => HashSet::from([value.clone()])
} }
} }
}
impl OppositeEq for Expression { pub fn eq(&self, other: &Self, ignore_case: bool) -> bool {
fn opposite_eq(&self, other: &Self) -> bool { match (self, other) {
(Expression::Not(left), Expression::Not(right)) => Expression::eq(left, right, ignore_case),
(Expression::Binary { left: left_left, operator: left_operator, right: left_right },
Expression::Binary { left: right_left, operator: right_operator, right: right_right }) => {
Expression::eq(left_left, right_left, ignore_case)
&& left_operator == right_operator
&& Expression::eq(left_right, right_right, ignore_case)
}
(Expression::Atomic(left), Expression::Atomic(right)) => {
if ignore_case {
left.eq_ignore_ascii_case(right)
} else {
left == right
}
}
_ => false
}
}
pub fn opposite_eq(&self, other: &Self, ignore_case: bool) -> bool {
match (self, other) { match (self, other) {
(Expression::Not(_), Expression::Not(_)) => false, (Expression::Not(_), Expression::Not(_)) => false,
(Expression::Not(left), right) => left.as_ref() == right, (Expression::Not(left), right) => left.as_ref().eq(right, ignore_case),
(left, Expression::Not(right)) => left == right.as_ref(), (left, Expression::Not(right)) => left.eq(right.as_ref(), ignore_case),
_ => false, _ => false,
} }
} }
@ -99,6 +113,48 @@ mod tests {
use crate::expressions::expression::Expression; use crate::expressions::expression::Expression;
use crate::expressions::helpers::{and, atomic, implies, not, or}; use crate::expressions::helpers::{and, atomic, implies, not, or};
#[test]
fn test_eq_ignore_case_atomics() {
let expression_lower = atomic("a");
let expression_upper = atomic("A");
assert!(expression_lower.eq(&expression_upper, true));
}
#[test]
fn test_eq_ignore_case_not() {
let expression_lower = not(atomic("a"));
let expression_upper = not(atomic("A"));
assert!(expression_lower.eq(&expression_upper, true));
}
#[test]
fn test_eq_ignore_case_and() {
let expression_lower = and(atomic("a"), atomic("b"));
let expression_upper = and(atomic("A"), atomic("B"));
assert!(expression_lower.eq(&expression_upper, true));
}
#[test]
fn test_eq_ignore_case_equal() {
let expression_lower = or(atomic("a"), atomic("b"));
let expression_upper = or(atomic("a"), atomic("b"));
assert!(expression_lower.eq(&expression_upper, true));
}
#[test]
fn test_eq_ignore_case_unequal() {
let expression_lower = or(atomic("a"), atomic("b"));
let expression_upper = or(atomic("a"), atomic("c"));
assert!(!expression_lower.eq(&expression_upper, true));
}
#[test]
fn test_eq_dont_ignore_case() {
let expression_lower = or(atomic("a"), atomic("b"));
let expression_upper = or(atomic("a"), atomic("B"));
assert!(!expression_lower.eq(&expression_upper, false));
}
#[test] #[test]
fn test_expression_a_and_not_b_display() { fn test_expression_a_and_not_b_display() {
let expression = and( let expression = and(

View File

@ -2,9 +2,10 @@ use std::ops::Deref;
use serde::Serialize; use serde::Serialize;
use crate::expressions::expression::{Expression, OppositeEq}; use crate::expressions::expression::Expression;
use crate::expressions::helpers::{and, binary, not, or}; use crate::expressions::helpers::{and, binary, not, or};
use crate::expressions::operator::BinaryOperator; use crate::expressions::operator::BinaryOperator;
use crate::routing::options::SimplifyOptions;
use crate::routing::response::Operation; use crate::routing::response::Operation;
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
@ -21,19 +22,19 @@ pub enum Law {
#[macro_export] #[macro_export]
macro_rules! absorption_law_opposites { macro_rules! absorption_law_opposites {
($left:expr, $right:expr, $operations:expr, $op:pat, $func:expr) => { ($left:expr, $right:expr, $operations:expr, $op:pat, $func:expr, $ignore_case:expr) => {
match ($left.as_ref(), $right.as_ref()) { match ($left.as_ref(), $right.as_ref()) {
(_, Expression::Binary { left: right_left, operator: $op, right: right_right }) => { (_, Expression::Binary { left: right_left, operator: $op, right: right_right }) => {
evaluate_equals_or_opposites($left.as_ref(), right_left, right_right, $func, $operations).unwrap_or( evaluate_equals_or_opposites($left.as_ref(), right_left, right_right, $func, $ignore_case, $operations).unwrap_or(
$func($left.absorption_law($operations), $right.absorption_law($operations)) $func($left.absorption_law($operations, $ignore_case), $right.absorption_law($operations, $ignore_case))
) )
} }
(Expression::Binary { left: left_left, operator: $op, right: left_right }, _) => { (Expression::Binary { left: left_left, operator: $op, right: left_right }, _) => {
evaluate_equals_or_opposites($right.as_ref(), left_left, left_right, $func, $operations).unwrap_or( evaluate_equals_or_opposites($right.as_ref(), left_left, left_right, $func, $ignore_case, $operations).unwrap_or(
$func($left.absorption_law($operations), $right.absorption_law($operations)) $func($left.absorption_law($operations, $ignore_case), $right.absorption_law($operations, $ignore_case))
) )
} }
(left, right) => $func(left.absorption_law($operations), right.absorption_law($operations)) (left, right) => $func(left.absorption_law($operations, $ignore_case), right.absorption_law($operations, $ignore_case))
} }
}; };
} }
@ -57,14 +58,24 @@ macro_rules! distribution_law_atomic_vs_binary {
}; };
} }
// TODO deduplicate code #[derive(Debug, Default)]
pub struct Options {
pub ignore_case: bool,
}
impl From<SimplifyOptions> for Options {
fn from(options: SimplifyOptions) -> Self {
Self { ignore_case: options.ignore_case }
}
}
impl Expression { impl Expression {
// TODO better track of operations // TODO better track of operations
pub fn simplify(&self) -> (Self, Vec<Operation>) { pub fn simplify(&self, options: Options) -> (Self, Vec<Operation>) {
let mut operations: Vec<Operation> = vec![]; let mut operations: Vec<Operation> = vec![];
let expression = self.elimination_of_implication(&mut operations) let expression = self.elimination_of_implication(&mut operations)
.de_morgans_laws(&mut operations) .de_morgans_laws(&mut operations)
.absorption_law(&mut operations) .absorption_law(&mut operations, options.ignore_case)
// .associative_law(&mut operations) // .associative_law(&mut operations)
.distribution_law(&mut operations) .distribution_law(&mut operations)
.double_negation_elimination(&mut operations); .double_negation_elimination(&mut operations);
@ -149,23 +160,24 @@ impl Expression {
result result
} }
fn absorption_law(&self, operations: &mut Vec<Operation>) -> Self { fn absorption_law(&self, operations: &mut Vec<Operation>, ignore_case: bool) -> Self {
let result = match self { let result = match self {
Expression::Binary { left, operator: BinaryOperator::And | BinaryOperator::Or, right } if left == right => { Expression::Binary { left, operator: BinaryOperator::And | BinaryOperator::Or, right }
left.absorption_law(operations) if Expression::eq(left, right, ignore_case) => {
left.absorption_law(operations, ignore_case)
} }
Expression::Binary { left, operator: BinaryOperator::And, right } => { Expression::Binary { left, operator: BinaryOperator::And, right } => {
absorption_law_opposites!(left, right, operations, BinaryOperator::Or, and) absorption_law_opposites!(left, right, operations, BinaryOperator::Or, and, ignore_case)
} }
Expression::Binary { left, operator: BinaryOperator::Or, right } => { Expression::Binary { left, operator: BinaryOperator::Or, right } => {
absorption_law_opposites!(left, right, operations, BinaryOperator::And, or) absorption_law_opposites!(left, right, operations, BinaryOperator::And, or, ignore_case)
} }
Expression::Binary { left, operator, right } => binary( Expression::Binary { left, operator, right } => binary(
left.absorption_law(operations), left.absorption_law(operations, ignore_case),
*operator, *operator,
right.absorption_law(operations), right.absorption_law(operations, ignore_case),
), ),
Expression::Not(expr) => not(expr.absorption_law(operations)), Expression::Not(expr) => not(expr.absorption_law(operations, ignore_case)),
atomic => atomic.clone(), atomic => atomic.clone(),
}; };
if let Some(operation) = Operation::new(self, &result, Law::AbsorptionLaw) { if let Some(operation) = Operation::new(self, &result, Law::AbsorptionLaw) {
@ -210,15 +222,16 @@ fn evaluate_equals_or_opposites<F: Fn(Expression, Expression) -> Expression>(
left: &Expression, left: &Expression,
right: &Expression, right: &Expression,
ret_func: F, ret_func: F,
ignore_case: bool,
operations: &mut Vec<Operation>, operations: &mut Vec<Operation>,
) -> Option<Expression> { ) -> Option<Expression> {
if this == left || this == right { if this.eq(left, ignore_case) || this.eq(right, ignore_case) {
return Some(this.absorption_law(operations)); return Some(this.absorption_law(operations, ignore_case));
} else if left.is_atomic() && right.is_atomic() && this.opposite_eq(left) { } else if left.is_atomic() && right.is_atomic() && this.opposite_eq(left, ignore_case) {
if this.opposite_eq(left) { if this.opposite_eq(left, ignore_case) {
return Some(ret_func(right.absorption_law(operations), this.absorption_law(operations))); return Some(ret_func(right.absorption_law(operations, ignore_case), this.absorption_law(operations, ignore_case)));
} else if this.opposite_eq(right) { } else if this.opposite_eq(right, ignore_case) {
return Some(ret_func(left.absorption_law(operations), this.absorption_law(operations))); return Some(ret_func(left.absorption_law(operations, ignore_case), this.absorption_law(operations, ignore_case)));
} }
} }
None None
@ -231,7 +244,7 @@ mod tests {
#[test] #[test]
fn test_simplify() { fn test_simplify() {
let (expression, operations) = implies(atomic("a"), atomic("b")).simplify(); let (expression, operations) = implies(atomic("a"), atomic("b")).simplify(Default::default());
assert_eq!(expression, or(not(atomic("a")), atomic("b"))); assert_eq!(expression, or(not(atomic("a")), atomic("b")));
assert_eq!(operations.len(), 1); assert_eq!(operations.len(), 1);
assert_eq!(operations[0].law, Law::EliminationOfImplication); assert_eq!(operations[0].law, Law::EliminationOfImplication);
@ -239,7 +252,7 @@ mod tests {
#[test] #[test]
fn test_simplify_a_and_a() { fn test_simplify_a_and_a() {
let (expression, operations) = and(atomic("a"), atomic("a")).simplify(); let (expression, operations) = and(atomic("a"), atomic("a")).simplify(Default::default());
assert_eq!(expression, atomic("a")); assert_eq!(expression, atomic("a"));
assert_eq!(operations.len(), 1); assert_eq!(operations.len(), 1);
assert_eq!(operations[0].law, Law::AbsorptionLaw); assert_eq!(operations[0].law, Law::AbsorptionLaw);
@ -247,7 +260,7 @@ mod tests {
#[test] #[test]
fn test_implication_and_de_morgans() { fn test_implication_and_de_morgans() {
let expression = implies(and(not(atomic("a")), atomic("b")), atomic("c")).simplify().0; let expression = implies(and(not(atomic("a")), atomic("b")), atomic("c")).simplify(Default::default()).0;
assert_eq!(expression, or(or(atomic("a"), not(atomic("b"))), atomic("c"))); assert_eq!(expression, or(or(atomic("a"), not(atomic("b"))), atomic("c")));
} }
@ -390,21 +403,21 @@ mod tests {
#[test] #[test]
fn test_absorption_law_and() { fn test_absorption_law_and() {
let mut operations = vec![]; let mut operations = vec![];
let expression = and(atomic("a"), or(atomic("a"), atomic("b"))).absorption_law(&mut operations); let expression = and(atomic("a"), or(atomic("a"), atomic("b"))).absorption_law(&mut operations, false);
assert_eq!(expression, atomic("a")); assert_eq!(expression, atomic("a"));
} }
#[test] #[test]
fn test_absorption_law_or() { fn test_absorption_law_or() {
let mut operations = vec![]; let mut operations = vec![];
let expression = or(atomic("a"), and(atomic("a"), atomic("b"))).absorption_law(&mut operations); let expression = or(atomic("a"), and(atomic("a"), atomic("b"))).absorption_law(&mut operations, false);
assert_eq!(expression, atomic("a")); assert_eq!(expression, atomic("a"));
} }
#[test] #[test]
fn test_absorption_law_nested_and() { fn test_absorption_law_nested_and() {
let mut operations = vec![]; let mut operations = vec![];
let expression = and(atomic("a"), or(atomic("a"), atomic("b"))).absorption_law(&mut operations); let expression = and(atomic("a"), or(atomic("a"), atomic("b"))).absorption_law(&mut operations, Default::default());
assert_eq!(expression, atomic("a")); assert_eq!(expression, atomic("a"));
} }
@ -412,7 +425,7 @@ mod tests {
#[test] #[test]
fn test_absorption_law_not() { fn test_absorption_law_not() {
let mut operations = vec![]; let mut operations = vec![];
let expression = or(and(not(atomic("a")), atomic("b")), atomic("a")).absorption_law(&mut operations); let expression = or(and(not(atomic("a")), atomic("b")), atomic("a")).absorption_law(&mut operations, Default::default());
assert_eq!(expression, or(atomic("b"), atomic("a"))); assert_eq!(expression, or(atomic("b"), atomic("a")));
} }
@ -420,7 +433,7 @@ mod tests {
#[test] #[test]
fn test_absorption_law_not_reversed() { fn test_absorption_law_not_reversed() {
let mut operations = vec![]; let mut operations = vec![];
let expression = or(and(atomic("a"), atomic("b")), not(atomic("a"))).absorption_law(&mut operations); let expression = or(and(atomic("a"), atomic("b")), not(atomic("a"))).absorption_law(&mut operations, Default::default());
assert_eq!(expression, or(atomic("b"), not(atomic("a")))); assert_eq!(expression, or(atomic("b"), not(atomic("a"))));
} }
@ -428,7 +441,7 @@ mod tests {
#[test] #[test]
fn test_absorption_law_double_not() { fn test_absorption_law_double_not() {
let mut operations = vec![]; let mut operations = vec![];
let expression = or(and(not(atomic("a")), atomic("b")), not(atomic("a"))).absorption_law(&mut operations); let expression = or(and(not(atomic("a")), atomic("b")), not(atomic("a"))).absorption_law(&mut operations, Default::default());
assert_eq!(expression, not(atomic("a"))); assert_eq!(expression, not(atomic("a")));
} }
@ -436,7 +449,7 @@ mod tests {
fn test_absorption_law_duplicate_atomic() { fn test_absorption_law_duplicate_atomic() {
let mut operations = vec![]; let mut operations = vec![];
let expression = and(atomic("A"), atomic("A")); let expression = and(atomic("A"), atomic("A"));
let simplified = expression.absorption_law(&mut operations); let simplified = expression.absorption_law(&mut operations, Default::default());
assert_eq!(simplified, atomic("A")); assert_eq!(simplified, atomic("A"));
assert_eq!(operations.len(), 1); assert_eq!(operations.len(), 1);
assert_eq!(operations[0].law, Law::AbsorptionLaw); assert_eq!(operations[0].law, Law::AbsorptionLaw);
@ -448,7 +461,7 @@ mod tests {
#[test] #[test]
fn test_in_parenthesis() { fn test_in_parenthesis() {
let mut operations = vec![]; let mut operations = vec![];
let expression = and(or(atomic("a"), atomic("b")), not(atomic("a"))).absorption_law(&mut operations); let expression = and(or(atomic("a"), atomic("b")), not(atomic("a"))).absorption_law(&mut operations, Default::default());
assert_eq!(expression, and(atomic("b"), not(atomic("a")))); assert_eq!(expression, and(atomic("b"), not(atomic("a"))));
} }

View File

@ -2,15 +2,17 @@ use serde::Deserialize;
use crate::expressions::truth_table::{Hide, Sort}; use crate::expressions::truth_table::{Hide, Sort};
use crate::utils::serialize::{ret_true, deserialize_bool}; use crate::utils::serialize::{ret_true, deserialize_bool};
// TODO deserialize_bool should not be necessary
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimplifyOptions { pub struct SimplifyOptions {
#[serde( #[serde(
default = "ret_true", default = "ret_true",
deserialize_with = "deserialize_bool" deserialize_with = "deserialize_bool"
)] )]
pub simplify: bool, pub simplify: bool,
#[serde(default = "ret_true")] #[serde(default, deserialize_with = "deserialize_bool")]
pub case_sensitive: bool, // TODO: Implement case sensitivity pub ignore_case: bool,
} }
#[derive(Deserialize, Default)] #[derive(Deserialize, Default)]

View File

@ -20,7 +20,7 @@ async fn simplify(Path(path): Path<String>, Query(query): Query<SimplifyOptions>
let before = expression.to_string(); let before = expression.to_string();
let mut operations = vec![]; let mut operations = vec![];
if query.simplify { if query.simplify {
(expression, operations) = expression.simplify(); (expression, operations) = expression.simplify(query.into());
} }
SimplifyResponse { SimplifyResponse {
before, before,
@ -42,7 +42,7 @@ async fn simplify_and_table(Path(path): Path<String>, Query(query): Query<Simpli
let before = expression.to_string(); let before = expression.to_string();
let mut operations = vec![]; let mut operations = vec![];
if query.simplify_options.simplify { if query.simplify_options.simplify {
(expression, operations) = expression.simplify(); (expression, operations) = expression.simplify(query.simplify_options.into());
} }
let truth_table = TruthTable::new(&expression, query.table_options); let truth_table = TruthTable::new(&expression, query.table_options);
SimplifyResponse { SimplifyResponse {