Evaluate in place expressions
This solution gives similar performance results than the tree based one, mostly because of the intermediate vectors.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
use crate::expr::are_valid_expressions;
|
||||
use crate::expr::is_valid_guess_of_tokens;
|
||||
use crate::game::Hand;
|
||||
use crate::lexer::lexer;
|
||||
use crate::parser::parse;
|
||||
use crate::lexer::{lexer_reuse, Token};
|
||||
use crate::tile::{Digit, Operator, Tile};
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -37,6 +36,7 @@ pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
||||
}
|
||||
|
||||
let mut trial: Vec<Tile> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
||||
let mut tokens: Vec<Token> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
||||
|
||||
// Generate all possible permutations, with an increasing number of tiles
|
||||
for nb_digits in 2..=numbers.len() {
|
||||
@@ -47,14 +47,12 @@ pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
||||
for nb_operators in 0..=(nb_digits - 2) {
|
||||
for operators in operators.iter().permutations(nb_operators) {
|
||||
merge_expression(&digits, &operators, equals_idx, &mut trial);
|
||||
if let Ok(tokens) = lexer(&trial) {
|
||||
if let Ok(expressions) = parse(&tokens) {
|
||||
if are_valid_expressions(&expressions) {
|
||||
combinations.push(trial.clone());
|
||||
}
|
||||
}
|
||||
lexer_reuse(&trial, &mut tokens);
|
||||
if is_valid_guess_of_tokens(&tokens) {
|
||||
combinations.push(trial.clone());
|
||||
}
|
||||
trial.clear();
|
||||
tokens.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
use crate::board::Board;
|
||||
use crate::lexer::lexer;
|
||||
use crate::lexer::{lexer, DecimalToken, Token};
|
||||
use crate::parser;
|
||||
use crate::parser::{Expression, Expressions};
|
||||
use crate::position::Position2d;
|
||||
use crate::tile::{Operator, Tile};
|
||||
|
||||
/// Evaluates a single expression syntax tree.
|
||||
pub fn calculate(expr: &Expression) -> f64 {
|
||||
match expr {
|
||||
Expression::Digit(value) => *value as f64,
|
||||
@@ -22,6 +23,7 @@ pub fn calculate(expr: &Expression) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates a vector of expression syntax trees.
|
||||
pub fn are_valid_expressions(expr: &Expressions) -> bool {
|
||||
let mut res: Option<f64> = None;
|
||||
for expr in expr {
|
||||
@@ -37,6 +39,31 @@ pub fn are_valid_expressions(expr: &Expressions) -> bool {
|
||||
res.is_some()
|
||||
}
|
||||
|
||||
/// Determines if the given tokens are a valid equation.
|
||||
pub fn is_valid_guess_of_tokens(tokens: &[Token]) -> bool {
|
||||
let mut res: Option<f64> = None;
|
||||
for part in tokens.split(|token| matches!(token, Token::Equals)) {
|
||||
let rpn = shunting_yard(part);
|
||||
if let Ok(rpn) = rpn {
|
||||
let value = evaluate_rpn(&rpn);
|
||||
if let Ok(value) = value {
|
||||
if let Some(res) = res {
|
||||
if res != value {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
res = Some(value);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
res.is_some()
|
||||
}
|
||||
|
||||
pub fn is_valid_guess(board: &Board, positions: &[Position2d]) -> Result<bool, ()> {
|
||||
let tiles = positions
|
||||
.iter()
|
||||
@@ -44,11 +71,74 @@ pub fn is_valid_guess(board: &Board, positions: &[Position2d]) -> Result<bool, (
|
||||
.collect::<Option<Vec<Tile>>>()
|
||||
.ok_or(())?;
|
||||
|
||||
let tokens = lexer(&tiles)?;
|
||||
let tokens = lexer(&tiles);
|
||||
let expressions = parser::parse(&tokens)?;
|
||||
Ok(are_valid_expressions(&expressions))
|
||||
}
|
||||
|
||||
/// Convert an infix expression to a postfix expression.
|
||||
fn shunting_yard(tokens: &[Token]) -> Result<Vec<DecimalToken>, ()> {
|
||||
let mut operator_stack: Vec<DecimalToken> = Vec::with_capacity(tokens.len());
|
||||
let mut output: Vec<DecimalToken> = Vec::with_capacity(tokens.len());
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
Token::NumberLiteral(num) => output.push(DecimalToken::NumberLiteral(*num as f64)),
|
||||
Token::Operator(op) => {
|
||||
while let Some(DecimalToken::Operator(top_op)) = operator_stack.last() {
|
||||
if top_op.precedence() >= op.precedence() {
|
||||
output.push(operator_stack.pop().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
operator_stack.push(DecimalToken::Operator(*op));
|
||||
}
|
||||
Token::LeftParen => operator_stack.push(DecimalToken::LeftParen),
|
||||
Token::RightParen => {
|
||||
while let Some(top_op) = operator_stack.pop() {
|
||||
if let DecimalToken::LeftParen = top_op {
|
||||
break;
|
||||
} else {
|
||||
output.push(top_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected token: {:?}", token),
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(top_op) = operator_stack.pop() {
|
||||
output.push(top_op);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Evaluate a postfix expression.
|
||||
fn evaluate_rpn(tokens: &[DecimalToken]) -> Result<f64, ()> {
|
||||
let mut stack = Vec::new();
|
||||
for token in tokens {
|
||||
match token {
|
||||
DecimalToken::NumberLiteral(num) => stack.push(*num),
|
||||
DecimalToken::Operator(op) => {
|
||||
let right = stack.pop().ok_or(())?;
|
||||
let left = stack.pop().ok_or(())?;
|
||||
let result = match op {
|
||||
Operator::Add => left + right,
|
||||
Operator::Subtract => left - right,
|
||||
Operator::Multiply => left * right,
|
||||
Operator::Divide => left / right,
|
||||
};
|
||||
stack.push(result);
|
||||
}
|
||||
_ => panic!("Unexpected token: {:?}", token),
|
||||
}
|
||||
}
|
||||
|
||||
stack.pop().ok_or(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -89,4 +179,76 @@ mod tests {
|
||||
];
|
||||
assert!(!are_valid_expressions(&expr));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shunting_yard_sample() {
|
||||
let tokens = vec![
|
||||
Token::NumberLiteral(5),
|
||||
Token::Operator(Operator::Multiply),
|
||||
Token::NumberLiteral(9),
|
||||
Token::Operator(Operator::Subtract),
|
||||
Token::NumberLiteral(2),
|
||||
];
|
||||
let res = shunting_yard(&tokens).expect("Failed to evaluate");
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
DecimalToken::NumberLiteral(5.),
|
||||
DecimalToken::NumberLiteral(9.),
|
||||
DecimalToken::Operator(Operator::Multiply),
|
||||
DecimalToken::NumberLiteral(2.),
|
||||
DecimalToken::Operator(Operator::Subtract),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shunting_yard_precedence() {
|
||||
let tokens = vec![
|
||||
Token::NumberLiteral(1),
|
||||
Token::Operator(Operator::Add),
|
||||
Token::NumberLiteral(2),
|
||||
Token::Operator(Operator::Multiply),
|
||||
Token::NumberLiteral(3),
|
||||
Token::Operator(Operator::Add),
|
||||
Token::NumberLiteral(4),
|
||||
];
|
||||
let res = shunting_yard(&tokens).expect("Failed to evaluate");
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
DecimalToken::NumberLiteral(1.),
|
||||
DecimalToken::NumberLiteral(2.),
|
||||
DecimalToken::NumberLiteral(3.),
|
||||
DecimalToken::Operator(Operator::Multiply),
|
||||
DecimalToken::Operator(Operator::Add),
|
||||
DecimalToken::NumberLiteral(4.),
|
||||
DecimalToken::Operator(Operator::Add),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_valid_guess_of_tiles_equals() {
|
||||
let tokens = vec![
|
||||
Token::NumberLiteral(4),
|
||||
Token::Operator(Operator::Subtract),
|
||||
Token::NumberLiteral(1),
|
||||
Token::Equals,
|
||||
Token::NumberLiteral(3),
|
||||
];
|
||||
assert!(is_valid_guess_of_tokens(&tokens));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_valid_guess_of_tiles_not_equals() {
|
||||
let tokens = vec![
|
||||
Token::NumberLiteral(8),
|
||||
Token::Operator(Operator::Divide),
|
||||
Token::NumberLiteral(4),
|
||||
Token::Equals,
|
||||
Token::NumberLiteral(5),
|
||||
];
|
||||
assert!(!is_valid_guess_of_tokens(&tokens));
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,13 @@ pub enum Token {
|
||||
Equals,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum DecimalToken {
|
||||
NumberLiteral(f64),
|
||||
Operator(Operator),
|
||||
LeftParen,
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -23,9 +30,14 @@ impl fmt::Display for Token {
|
||||
}
|
||||
|
||||
/// Tokenize a sequence of tiles into tokens.
|
||||
pub fn lexer(input: &[Tile]) -> Result<Vec<Token>, ()> {
|
||||
pub fn lexer(input: &[Tile]) -> Vec<Token> {
|
||||
let mut result = Vec::new();
|
||||
lexer_reuse(input, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Tokenize a sequence of tiles into tokens.
|
||||
pub fn lexer_reuse(input: &[Tile], result: &mut Vec<Token>) {
|
||||
let mut it = input.iter().peekable();
|
||||
while let Some(&c) = it.peek() {
|
||||
match c {
|
||||
@@ -62,7 +74,6 @@ pub fn lexer(input: &[Tile]) -> Result<Vec<Token>, ()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -88,7 +99,7 @@ mod tests {
|
||||
Token::NumberLiteral(456),
|
||||
Token::Equals,
|
||||
];
|
||||
assert_eq!(lexer(&input).unwrap(), expected);
|
||||
assert_eq!(lexer(&input), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -117,7 +128,7 @@ mod tests {
|
||||
Token::NumberLiteral(3),
|
||||
Token::RightParen,
|
||||
];
|
||||
assert_eq!(lexer(&input).unwrap(), expected);
|
||||
assert_eq!(lexer(&input), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -141,6 +152,6 @@ mod tests {
|
||||
Token::NumberLiteral(1234),
|
||||
Token::RightParen,
|
||||
];
|
||||
assert_eq!(lexer(&input).unwrap(), expected);
|
||||
assert_eq!(lexer(&input), expected);
|
||||
}
|
||||
}
|
||||
|
@@ -68,6 +68,15 @@ pub enum Operator {
|
||||
Divide,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Operator::Add | Operator::Subtract => 1,
|
||||
Operator::Multiply | Operator::Divide => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Operator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
Reference in New Issue
Block a user