use crate::expression::{
ast::{BinaryOpr, EXPR_EPSILON, ExprNode, ExprNum, ExprTree},
error::AstError,
};
use std::collections::HashSet;
struct StackFrame {
l_idx: usize,
r_idx: usize,
opr_idx: usize,
has_swapped: bool,
node_indices: Vec<usize>,
}
impl StackFrame {
fn with_capacity(capacity: usize) -> Self {
StackFrame {
l_idx: 0,
r_idx: 1,
opr_idx: 0,
has_swapped: false,
node_indices: Vec::with_capacity(capacity),
}
}
}
pub fn solve24<'a>(
nums: &[ExprNum],
oprs: &[BinaryOpr],
target: ExprNum,
limit: usize,
) -> Result<HashSet<String>, AstError<'a>> {
let mut stack = Vec::with_capacity(nums.len());
let mut tree = ExprTree::new();
let mut solutions = HashSet::new();
for (i, n) in nums.iter().enumerate() {
stack.push(StackFrame::with_capacity(nums.len() - i));
stack[0]
.node_indices
.push(tree.add_node(ExprNode::Literal(*n))?);
}
let mut cur_frame_idx: usize = 0;
let top_frame_idx = stack.len() - 1; loop {
if limit != 0 && limit <= solutions.len() {
break;
}
if cur_frame_idx >= top_frame_idx {
if let Ok(Some(final_result)) = tree.get_pre_value(stack[cur_frame_idx].node_indices[0])
{
if (target - final_result).abs() < EXPR_EPSILON {
solutions.insert(tree.to_string());
}
}
cur_frame_idx -= 1;
tree.pop_node();
continue;
} else {
let cur_frame = &mut stack[cur_frame_idx];
if cur_frame.l_idx >= (cur_frame.node_indices.len() - 1) {
if cur_frame_idx == 0 {
break; }
cur_frame_idx -= 1;
tree.pop_node();
cur_frame.l_idx = 0;
cur_frame.r_idx = 1;
continue;
}
let (l_idx, r_idx) = if cur_frame.has_swapped {
(
cur_frame.node_indices[cur_frame.r_idx],
cur_frame.node_indices[cur_frame.l_idx],
)
} else {
(
cur_frame.node_indices[cur_frame.l_idx],
cur_frame.node_indices[cur_frame.r_idx],
)
};
let new_root_idx = tree.add_root(oprs[cur_frame.opr_idx], l_idx, r_idx)?;
if let Ok(Some(_)) = tree.get_pre_value(new_root_idx) {
let (cur_stack, next_stack) = stack.split_at_mut(cur_frame_idx + 1);
let (cur_frame, next_frame) = (&mut cur_stack[cur_frame_idx], &mut next_stack[0]);
next_frame.node_indices.clear();
next_frame.node_indices.push(new_root_idx);
for &idx in cur_frame.node_indices.iter() {
if idx != l_idx && idx != r_idx {
next_frame.node_indices.push(idx);
}
}
}
}
let cur_frame = &mut stack[cur_frame_idx];
if !cur_frame.has_swapped && !oprs[cur_frame.opr_idx].is_commutation() {
cur_frame.has_swapped = true;
cur_frame_idx += 1; continue;
}
cur_frame.has_swapped = false;
cur_frame.opr_idx += 1;
if cur_frame.opr_idx < oprs.len() {
cur_frame_idx += 1;
continue;
}
cur_frame.opr_idx = 0;
cur_frame.r_idx += 1;
if cur_frame.r_idx < cur_frame.node_indices.len() {
cur_frame_idx += 1;
continue;
}
cur_frame.l_idx += 1;
cur_frame.r_idx = cur_frame.l_idx + 1;
cur_frame_idx += 1;
}
Ok(solutions)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_solution() {
let nums = [1.0, 1.0, 1.0, 1.0];
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
assert_eq!(solutions.len(), 0, "1,1,1,1不能解出24");
}
#[test]
fn test_2numbers_solution() {
let nums = [4.0, 6.0];
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
assert!(solutions.len() > 0, "4,6能解出24");
}
#[test]
fn test_3numbers_solution() {
let nums = [3.0, 8.0, 1.0];
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
assert!(solutions.len() > 0, "3,8,1能解出24");
}
#[test]
fn test_4numbers_solution() {
let nums = [5.0, 5.0, 4.0, 1.0];
let start = std::time::Instant::now();
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
let duration = start.elapsed();
println!("runtime-new: {}", duration.as_nanos());
let count = solutions.len();
assert!(count > 0, "5,5,4,1能解出24");
println!("找到 {} 个解法", count);
}
#[test]
fn test_5numbers_solution() {
let nums = [1.0, 2.0, 3.0, 4.0, 5.0];
let start = std::time::Instant::now();
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
let duration = start.elapsed();
println!("runtime-new: {}", duration.as_nanos());
let count = solutions.len();
assert!(count > 0, "1,2,3,4,5能解出24");
println!("找到 {} 个解法", count);
}
#[test]
fn test_6numbers_solution() {
let nums = [1.0, 1.0, 1.0, 1.0, 1.0, 5.0];
let start = std::time::Instant::now();
let solutions = solve24(&nums, &BinaryOpr::get_all_operators(), 24.0, 0).unwrap();
let duration = start.elapsed();
println!("runtime-new: {}", duration.as_millis());
let count = solutions.len();
assert!(count > 0, "1,1,1,1,1,5能解出24");
println!("找到 {} 个解法", count);
}
}