Fixed truth table generation

This commit is contained in:
Martin Berg Alstad 2024-06-13 11:34:57 +02:00
parent 5c8f602d1d
commit 3ad1ad53fc
7 changed files with 197 additions and 45 deletions

View File

@ -27,3 +27,11 @@ GET {{url}}/simplify/!A
expression("A => B")
%}
GET {{url}}/simplify/{{expression}}
### GET with table
< {%
import {expression} from "./common";
expression("A & B | C")
%}
GET {{url}}/simplify/table/{{expression}}

View File

@ -1,3 +1,4 @@
use std::collections::HashSet;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::expressions::iterator::ExpressionIterator;
@ -9,7 +10,7 @@ pub trait OppositeEq {
fn opposite_eq(&self, other: &Self) -> bool;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Expression {
Not(Box<Expression>),
@ -34,6 +35,18 @@ impl Expression {
}
}
pub fn get_atomic_values(&self) -> HashSet<String> {
match self {
Expression::Not(expr) => expr.get_atomic_values(),
Expression::Binary { left, right, .. } => {
let mut values = left.get_atomic_values();
values.extend(right.get_atomic_values());
values
}
Expression::Atomic(value) => HashSet::from([value.clone()])
}
}
pub fn count_distinct(&self) -> usize {
self.count_distinct_with_visited(vec![].as_mut())
}

View File

@ -3,5 +3,5 @@ pub mod operator;
#[macro_use]
pub mod helpers;
pub mod simplify;
mod truth_table;
pub mod truth_table;
mod iterator;

View File

@ -1,9 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinaryOperator {
Implication,
Or,
And,
}
impl BinaryOperator {
pub fn eval(&self, left: bool, right: bool) -> bool {
match self {
BinaryOperator::And => left && right,
BinaryOperator::Or => left || right,
BinaryOperator::Implication => !left || right,
}
}
}

View File

@ -1,9 +1,9 @@
use std::slice::Iter;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::expressions::expression::Expression;
use crate::expressions::operator::BinaryOperator;
use crate::map;
use crate::utils::array::Distinct;
type TruthMatrix = Vec<Vec<bool>>;
@ -40,9 +40,10 @@ pub struct TruthTableOptions {
}
impl TruthTable {
// TODO options
pub fn new(expression: &Expression, options: TruthTableOptions) -> Self {
let header = Self::extract_header(expression);
let truth_matrix = Self::generate_truth_matrix(expression);
let truth_matrix = Self::generate_truth_matrix(expression, &header);
Self { header, truth_matrix }
}
@ -78,15 +79,20 @@ impl TruthTable {
}
}
fn generate_truth_matrix(expression: &Expression) -> TruthMatrix {
let count = expression.count_distinct();
if count == 0 {
fn generate_truth_matrix(expression: &Expression, header: &[String]) -> TruthMatrix {
let mut atomics = expression.get_atomic_values()
.into_iter().collect::<Vec<String>>();
if atomics.is_empty() {
return vec![];
}
Self::truth_combinations(count)
.iter().map(|combo| {
Self::resolve_expression(expression, &mut combo.iter())
}).collect()
atomics.sort();
Self::truth_combinations(atomics.len()).iter()
.map(|combo| {
Self::resolve_expression(expression, &atomics.iter()
.enumerate()
.map(|(index, value)| (value.clone(), combo[index]))
.collect(), header)
}).collect()
}
fn truth_combinations(count: usize) -> TruthMatrix {
@ -97,31 +103,41 @@ impl TruthTable {
).collect()
}
fn resolve_expression(expression: &Expression, booleans: &mut Iter<bool>) -> Vec<bool> {
fn resolve_expression(expression: &Expression, booleans: &HashMap<String, bool>, header: &[String]) -> Vec<bool> {
let expression_map = Self::_resolve_expression(expression, booleans);
let string_map = expression_map.iter()
.map(|(key, value)| (key.to_string(), *value))
.collect::<HashMap<String, bool>>();
header.iter()
.map(|s_expr| string_map.get(s_expr).copied().expect("Expression not found in map"))
.collect()
}
fn _resolve_expression<'a>(expression: &'a Expression, booleans: &HashMap<String, bool>) -> HashMap<&'a Expression, bool> {
match expression {
Expression::Not(expr) => {
Self::resolve_expression(expr, booleans)
.iter().map(|value| !value).collect()
not @ Expression::Not(expr) => {
let mut map = Self::_resolve_expression(expr, booleans);
if let Some(value) = map.get(expr.as_ref()) {
map.insert(not, !value);
}
map
}
Expression::Binary { left, right, .. } => {
let left_values = Self::resolve_expression(left, booleans);
let right_values = Self::resolve_expression(right, booleans);
left_values.iter()
.zip(right_values.iter())
.flat_map(|(left_value, right_value)| {
[*left_value, *right_value, match expression {
Expression::Binary { operator: BinaryOperator::And, .. } => *left_value && *right_value,
Expression::Binary { operator: BinaryOperator::Or, .. } => *left_value || *right_value,
Expression::Binary { operator: BinaryOperator::Implication, .. } => !*left_value || *right_value,
_ => false,
}]
}).collect()
binary @ Expression::Binary { left, right, operator } => {
let left_map = Self::_resolve_expression(left, booleans);
let right_map = Self::_resolve_expression(right, booleans);
let mut map = left_map;
map.extend(right_map);
if let (Some(left_value), Some(right_value)) = (map.get(left.as_ref()), map.get(right.as_ref())) {
map.insert(binary, operator.eval(*left_value, *right_value));
}
map
}
Expression::Atomic(_) => {
if let Some(value) = booleans.next() {
vec![*value]
atomic @ Expression::Atomic(value) => {
if let Some(value) = booleans.get(value) {
map!(atomic => *value)
} else {
vec![]
unreachable!("Atomic value not found in booleans")
}
}
}
@ -131,13 +147,21 @@ impl TruthTable {
#[cfg(test)]
mod tests {
use crate::matrix;
use super::*;
// TODO fails sometimes...
#[test]
fn test_new_truth_table() {
let expression = and!(atomic!("A"), atomic!("B"));
let truth_table = TruthTable::new(&expression, Default::default());
assert_eq!(truth_table.header, vec!["A", "B", "A ⋀ B"]);
assert_ne!(truth_table.truth_matrix, matrix![
true, true, true;
false, true, false;
true, false, false;
false, false, false
]);
assert_eq!(truth_table.truth_matrix, matrix![
true, true, true;
true, false, false;
@ -147,7 +171,37 @@ mod tests {
}
#[test]
fn test_truth_combinations() {
fn test_new_truth_table_a_and_b_or_c() {
let expression = and!(or!(atomic!("A"), atomic!("C")), or!(atomic!("B"), atomic!("C")));
let truth_table = TruthTable::new(&expression, Default::default());
let atomics = 3;
assert_eq!(truth_table.header, vec!["A", "C", "(A C)", "B", "(B C)", "(A C) ⋀ (B C)"]);
assert_eq!(truth_table.truth_matrix.len(), 2usize.pow(atomics as u32));
assert_eq!(truth_table.truth_matrix[0].len(), 6);
assert_eq!(truth_table.truth_matrix[0], vec![true, true, true, true, true, true]);
assert_eq!(truth_table.truth_matrix[1], vec![true, false, true, true, true, true]);
assert_eq!(truth_table.truth_matrix[2], vec![true, true, true, false, true, true]);
assert_eq!(truth_table.truth_matrix[3], vec![true, false, true, false, false, false]);
assert_eq!(truth_table.truth_matrix[4], vec![false, true, true, true, true, true]);
assert_eq!(truth_table.truth_matrix[5], vec![false, false, false, true, true, false]);
assert_eq!(truth_table.truth_matrix[6], vec![false, true, true, false, true, true]);
assert_eq!(truth_table.truth_matrix[7], vec![false, false, false, false, false, false]);
}
#[test]
fn test_truth_combinations_2() {
let combinations = TruthTable::truth_combinations(2);
assert_eq!(combinations, matrix![
true, true;
true, false;
false, true;
false, false
]);
}
#[test]
fn test_truth_combinations_3() {
let combinations = TruthTable::truth_combinations(3);
assert_eq!(combinations, matrix![
true, true, true;
@ -164,27 +218,62 @@ mod tests {
#[test]
fn test_resolve_expression_and_all_true() {
let expression = and!(atomic!("A"), atomic!("B"));
let booleans = [true, true];
let values = TruthTable::resolve_expression(&expression, &mut booleans.iter());
let booleans = map!["A".into() => true, "B".into() => true];
let header = vec!["A".into(), "B".into(), "A ⋀ B".into()];
let values = TruthTable::resolve_expression(&expression, &booleans, &header);
assert_eq!(values, vec![true, true, true]);
}
#[test]
fn test_resolve_expression_and_1_true_1_false() {
let expression = and!(atomic!("A"), atomic!("B"));
let booleans = [true, false];
let values = TruthTable::resolve_expression(&expression, &mut booleans.iter());
let booleans = map!["A".into() => true, "B".into() => false];
let header = vec!["A".into(), "B".into(), "A ⋀ B".into()];
let values = TruthTable::resolve_expression(&expression, &booleans, &header);
assert_eq!(values, vec![true, false, false]);
}
#[test]
fn test_resolve_expression_or_1_true_1_false() {
let expression = or!(atomic!("A"), atomic!("B"));
let booleans = [true, false];
let values = TruthTable::resolve_expression(&expression, &mut booleans.iter());
let booleans = map!["A".into() => true, "B".into() => false];
let header = vec!["A".into(), "B".into(), "(A B)".into()];
let values = TruthTable::resolve_expression(&expression, &booleans, &header);
assert_eq!(values, vec![true, false, true]);
}
#[test]
fn test_resolve_expression_duplicate_atomic() {
let expression = and!(atomic!("A"), atomic!("A"));
let booleans = map!["A".into() => true];
let header = vec!["A".into(), "A ⋀ A".into()];
let values = TruthTable::resolve_expression(&expression, &booleans, &header);
assert_eq!(values, vec![true, true]);
}
#[test]
fn test_resolve_expression_even_more_duplicates() {
let expression = and!(atomic!("A"), and!(atomic!("A"), and!(atomic!("A"), atomic!("A"))));
let booleans = HashMap::from([("A".into(), true)]);
let header = vec!["A".into(), "A ⋀ A".into(), "A ⋀ A ⋀ A".into(), "A ⋀ A ⋀ A ⋀ A".into()];
let values = TruthTable::resolve_expression(&expression, &booleans, &header);
assert_eq!(values, vec![true, true, true, true]);
}
#[test]
fn _test_resolve_expression_even_more_duplicates() {
let expression = and!(atomic!("A"), and!(atomic!("A"), and!(atomic!("A"), atomic!("A"))));
let booleans = HashMap::from([("A".into(), true)]);
let values = TruthTable::_resolve_expression(&expression, &booleans);
assert_eq!(values, HashMap::from([
(&atomic!("A"), true),
(&and!(atomic!("A"), atomic!("A")), true),
(&and!(atomic!("A"), and!(atomic!("A"), atomic!("A"))), true),
(&and!(atomic!("A"), and!(atomic!("A"), and!(atomic!("A"), atomic!("A")))), true),
]));
}
#[test]
fn test_atomic_expression() {
let expression = atomic!("A");

View File

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::expressions::expression::Expression;
use crate::expressions::simplify::Simplify;
use crate::expressions::truth_table::{TruthTable, TruthTableOptions};
use crate::language::{AcceptLanguage, Language};
pub fn router() -> Router<()> {
@ -38,6 +39,8 @@ struct SimplifyResponse {
after: String,
order_of_operations: Vec<String>,
expression: Expression,
#[serde(skip_serializing_if = "Option::is_none")]
truth_table: Option<TruthTable>,
}
// TODO
@ -52,12 +55,29 @@ async fn simplify(Path(path): Path<String>, query: Query<QueryOptions>, accept_l
after: expression.to_string(),
order_of_operations: vec![], // TODO
expression,
truth_table: None,
}).into_response()
} else {
(StatusCode::BAD_REQUEST, "Invalid expression").into_response()
}
}
async fn simplify_and_table() {
unimplemented!("Not yet implemented")
async fn simplify_and_table(Path(path): Path<String>, query: Query<QueryOptions>, accept_language: Option<AcceptLanguage>) -> Response {
if let Ok(mut expression) = Expression::try_from(path.as_str()) {
let before = expression.to_string();
if query.simplify {
expression = expression.simplify();
}
// TODO options
let truth_table = TruthTable::new(&expression, TruthTableOptions::default());
Json(SimplifyResponse {
before,
after: expression.to_string(),
order_of_operations: vec![], // TODO
expression,
truth_table: Some(truth_table),
}).into_response()
} else {
(StatusCode::BAD_REQUEST, "Invalid expression").into_response()
}
}

View File

@ -1,5 +1,3 @@
use std::cmp::max;
#[macro_export]
macro_rules! set {
() => { std::collections::HashSet::new() };
@ -31,6 +29,20 @@ macro_rules! matrix {
};
}
#[macro_export]
macro_rules! map {
() => { std::collections::HashMap::new() };
($($k:expr => $v:expr),*) => {
{
let mut temp_map = std::collections::HashMap::new();
$(
temp_map.insert($k, $v);
)*
temp_map
}
};
}
pub trait Distinct {
fn distinct(&mut self);