Ignore Case function
This commit is contained in:
parent
d9c0d90af4
commit
e7f1ae156d
@ -123,3 +123,18 @@ GET {{url}}/simplify/{{expression}}
|
||||
### GET with simplify="true"
|
||||
|
||||
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'");
|
||||
});
|
||||
%}
|
||||
|
@ -7,10 +7,6 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::expressions::operator::BinaryOperator;
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Expression {
|
||||
@ -39,14 +35,32 @@ impl Expression {
|
||||
Expression::Atomic(value) => HashSet::from([value.clone()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OppositeEq for Expression {
|
||||
fn opposite_eq(&self, other: &Self) -> bool {
|
||||
pub fn eq(&self, other: &Self, ignore_case: bool) -> 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) {
|
||||
(Expression::Not(_), Expression::Not(_)) => false,
|
||||
(Expression::Not(left), right) => left.as_ref() == right,
|
||||
(left, Expression::Not(right)) => left == right.as_ref(),
|
||||
(Expression::Not(left), right) => left.as_ref().eq(right, ignore_case),
|
||||
(left, Expression::Not(right)) => left.eq(right.as_ref(), ignore_case),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -99,6 +113,48 @@ mod tests {
|
||||
use crate::expressions::expression::Expression;
|
||||
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]
|
||||
fn test_expression_a_and_not_b_display() {
|
||||
let expression = and(
|
||||
|
@ -2,9 +2,10 @@ use std::ops::Deref;
|
||||
|
||||
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::operator::BinaryOperator;
|
||||
use crate::routing::options::SimplifyOptions;
|
||||
use crate::routing::response::Operation;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
@ -21,19 +22,19 @@ pub enum Law {
|
||||
|
||||
#[macro_export]
|
||||
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()) {
|
||||
(_, 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(
|
||||
$func($left.absorption_law($operations), $right.absorption_law($operations))
|
||||
evaluate_equals_or_opposites($left.as_ref(), right_left, right_right, $func, $ignore_case, $operations).unwrap_or(
|
||||
$func($left.absorption_law($operations, $ignore_case), $right.absorption_law($operations, $ignore_case))
|
||||
)
|
||||
}
|
||||
(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(
|
||||
$func($left.absorption_law($operations), $right.absorption_law($operations))
|
||||
evaluate_equals_or_opposites($right.as_ref(), left_left, left_right, $func, $ignore_case, $operations).unwrap_or(
|
||||
$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 {
|
||||
// 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 expression = self.elimination_of_implication(&mut operations)
|
||||
.de_morgans_laws(&mut operations)
|
||||
.absorption_law(&mut operations)
|
||||
.absorption_law(&mut operations, options.ignore_case)
|
||||
// .associative_law(&mut operations)
|
||||
.distribution_law(&mut operations)
|
||||
.double_negation_elimination(&mut operations);
|
||||
@ -149,23 +160,24 @@ impl Expression {
|
||||
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 {
|
||||
Expression::Binary { left, operator: BinaryOperator::And | BinaryOperator::Or, right } if left == right => {
|
||||
left.absorption_law(operations)
|
||||
Expression::Binary { left, operator: BinaryOperator::And | BinaryOperator::Or, right }
|
||||
if Expression::eq(left, right, ignore_case) => {
|
||||
left.absorption_law(operations, ignore_case)
|
||||
}
|
||||
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 } => {
|
||||
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(
|
||||
left.absorption_law(operations),
|
||||
left.absorption_law(operations, ignore_case),
|
||||
*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(),
|
||||
};
|
||||
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,
|
||||
right: &Expression,
|
||||
ret_func: F,
|
||||
ignore_case: bool,
|
||||
operations: &mut Vec<Operation>,
|
||||
) -> Option<Expression> {
|
||||
if this == left || this == right {
|
||||
return Some(this.absorption_law(operations));
|
||||
} else if left.is_atomic() && right.is_atomic() && this.opposite_eq(left) {
|
||||
if this.opposite_eq(left) {
|
||||
return Some(ret_func(right.absorption_law(operations), this.absorption_law(operations)));
|
||||
} else if this.opposite_eq(right) {
|
||||
return Some(ret_func(left.absorption_law(operations), this.absorption_law(operations)));
|
||||
if this.eq(left, ignore_case) || this.eq(right, ignore_case) {
|
||||
return Some(this.absorption_law(operations, ignore_case));
|
||||
} else if left.is_atomic() && right.is_atomic() && this.opposite_eq(left, ignore_case) {
|
||||
if this.opposite_eq(left, ignore_case) {
|
||||
return Some(ret_func(right.absorption_law(operations, ignore_case), this.absorption_law(operations, ignore_case)));
|
||||
} else if this.opposite_eq(right, ignore_case) {
|
||||
return Some(ret_func(left.absorption_law(operations, ignore_case), this.absorption_law(operations, ignore_case)));
|
||||
}
|
||||
}
|
||||
None
|
||||
@ -231,7 +244,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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!(operations.len(), 1);
|
||||
assert_eq!(operations[0].law, Law::EliminationOfImplication);
|
||||
@ -239,7 +252,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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!(operations.len(), 1);
|
||||
assert_eq!(operations[0].law, Law::AbsorptionLaw);
|
||||
@ -247,7 +260,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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")));
|
||||
}
|
||||
|
||||
@ -390,21 +403,21 @@ mod tests {
|
||||
#[test]
|
||||
fn test_absorption_law_and() {
|
||||
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"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absorption_law_or() {
|
||||
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"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absorption_law_nested_and() {
|
||||
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"));
|
||||
}
|
||||
|
||||
@ -412,7 +425,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_absorption_law_not() {
|
||||
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")));
|
||||
}
|
||||
|
||||
@ -420,7 +433,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_absorption_law_not_reversed() {
|
||||
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"))));
|
||||
}
|
||||
|
||||
@ -428,7 +441,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_absorption_law_double_not() {
|
||||
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")));
|
||||
}
|
||||
|
||||
@ -436,7 +449,7 @@ mod tests {
|
||||
fn test_absorption_law_duplicate_atomic() {
|
||||
let mut operations = vec![];
|
||||
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!(operations.len(), 1);
|
||||
assert_eq!(operations[0].law, Law::AbsorptionLaw);
|
||||
@ -448,7 +461,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_in_parenthesis() {
|
||||
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"))));
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,17 @@ use serde::Deserialize;
|
||||
use crate::expressions::truth_table::{Hide, Sort};
|
||||
use crate::utils::serialize::{ret_true, deserialize_bool};
|
||||
|
||||
// TODO deserialize_bool should not be necessary
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimplifyOptions {
|
||||
#[serde(
|
||||
default = "ret_true",
|
||||
deserialize_with = "deserialize_bool"
|
||||
)]
|
||||
pub simplify: bool,
|
||||
#[serde(default = "ret_true")]
|
||||
pub case_sensitive: bool, // TODO: Implement case sensitivity
|
||||
#[serde(default, deserialize_with = "deserialize_bool")]
|
||||
pub ignore_case: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
|
@ -20,7 +20,7 @@ async fn simplify(Path(path): Path<String>, Query(query): Query<SimplifyOptions>
|
||||
let before = expression.to_string();
|
||||
let mut operations = vec![];
|
||||
if query.simplify {
|
||||
(expression, operations) = expression.simplify();
|
||||
(expression, operations) = expression.simplify(query.into());
|
||||
}
|
||||
SimplifyResponse {
|
||||
before,
|
||||
@ -42,7 +42,7 @@ async fn simplify_and_table(Path(path): Path<String>, Query(query): Query<Simpli
|
||||
let before = expression.to_string();
|
||||
let mut operations = vec![];
|
||||
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);
|
||||
SimplifyResponse {
|
||||
|
Loading…
x
Reference in New Issue
Block a user