#![feature(test)] extern crate test; use aoc2020::common::*; use itertools::Itertools; use std::collections::VecDeque; type Parsed = Vec>; #[derive(Debug, Copy, Clone, PartialEq)] enum Operation { Number(usize), Multiply, Add, OpenParen, CloseParen, } fn read_input() -> String { read_file(18) } fn parse_input(raw: &str) -> Parsed { raw.lines() .map(|l| { l.bytes() .filter(|&b| b != b' ') .map(|op| match op { b'+' => Operation::Add, b'*' => Operation::Multiply, b'(' => Operation::OpenParen, b')' => Operation::CloseParen, n => Operation::Number((n - b'0') as usize), }) .collect() }) .collect() } fn part1(parsed: &Parsed) -> usize { parsed.iter().map(|l| build_stack(l, execute_stack_p1)).sum() } fn execute_stack_p1(mut stack: VecDeque) -> usize { while stack.len() > 2 { let res = match stack.drain(..3).next_tuple() { Some((Operation::Number(x), Operation::Add, Operation::Number(y))) => Operation::Number(x + y), Some((Operation::Number(x), Operation::Multiply, Operation::Number(y))) => Operation::Number(x * y), x => unreachable!("Invalid stack: {:?}", x), }; stack.push_front(res); } if let Operation::Number(n) = stack[0] { n } else { unreachable!() } } fn execute_stack_p2(mut stack: VecDeque) -> usize { while let Some(i) = stack.iter().position(|o| o == &Operation::Add) { let res = match stack.drain(i - 1..=i + 1).next_tuple() { Some((Operation::Number(x), Operation::Add, Operation::Number(y))) => Operation::Number(x + y), x => unreachable!("Invalid stack: {:?}", x), }; stack.insert(i - 1, res); } while stack.len() > 2 { let res = match stack.drain(..3).next_tuple() { Some((Operation::Number(x), Operation::Multiply, Operation::Number(y))) => Operation::Number(x * y), x => unreachable!("Invalid stack: {:?}", x), }; stack.push_front(res); } if let Operation::Number(n) = stack[0] { n } else { unreachable!() } } fn build_stack) -> usize + Copy>(ops: &[Operation], execute_stack: F) -> usize { let mut stack = VecDeque::new(); let mut parens = 0; for i in 0..ops.len() { match ops[i] { Operation::OpenParen if parens > 0 => parens += 1, #[rustfmt::skip] Operation::CloseParen => if parens > 0 { parens -= 1 } else { break }, // skip the entries that one of the recursive children is already processing _ if parens > 0 => continue, Operation::Add => stack.push_back(ops[i]), Operation::Multiply => stack.push_back(ops[i]), Operation::Number(n) => stack.push_back(Operation::Number(n)), Operation::OpenParen => { stack.push_back(Operation::Number(build_stack(&ops[i + 1..], execute_stack))); parens += 1; } } } execute_stack(stack) } fn part2(parsed: &Parsed) -> usize { parsed.iter().map(|l| build_stack(l, execute_stack_p2)).sum() } fn main() { let input = parse_input(&read_input()); println!("Part 1: {}", part1(&input)); println!("Part 2: {}", part2(&input)); } #[cfg(test)] mod tests { use super::*; use aoc2020::*; use paste::paste; use test::black_box; const TEST_INPUT: &str = "((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2"; test!(part1() == 13632); test!(part2() == 23340); bench!(part1() == 510009915468); bench!(part2() == 321176691637769); bench_input!(len == 375); }