use itertools::Itertools; pub fn day7_part1(input: &str) -> String { let input = parse(input); let sum: u64 = input .into_iter() .filter(|(res, operands)| possible(*res, operands)) .map(|(res, _)| res) .sum(); sum.to_string() } pub fn day7_part2(input: &str) -> String { let input = parse(input); let sum: u64 = input .into_iter() .filter(|(res, operands)| possible_with_concatenation(*res, operands)) .map(|(res, _)| res) .sum(); sum.to_string() } fn possible(result: u64, operands: &[u64]) -> bool { let operator_count = operands.len() - 1; (0..operator_count) .map(|_| [Operator::Plus, Operator::Times]) .multi_cartesian_product() .any(|operators| check(result, operands, operators.as_slice())) } fn possible_with_concatenation(result: u64, operands: &[u64]) -> bool { let operator_count = operands.len() - 1; (0..operator_count) .map(|_| [Operator::Plus, Operator::Times, Operator::Concatenate]) .multi_cartesian_product() .any(|operators| check(result, operands, operators.as_slice())) } fn check(result: u64, operands: &[u64], operators: &[Operator]) -> bool { let mut accumulator = operands[0]; for (operator, operand) in operators.iter().zip(&operands[1..]) { match operator { Operator::Times => accumulator *= operand, Operator::Plus => accumulator += operand, Operator::Concatenate => accumulator = concatenate(accumulator, *operand), } } result == accumulator } fn concatenate(left: u64, right: u64) -> u64 { let decimal_places_shift = right.ilog10() + 1; left * 10u64.pow(decimal_places_shift) + right } #[test] fn test_concatenate() { assert_eq!(concatenate(15, 6), 156) } #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum Operator { Times, Plus, Concatenate, } fn parse(input: &str) -> Vec<(u64, Vec)> { input .lines() .map(|line| line.split_once(": ").unwrap()) .map(|(res, operands)| { ( res.parse().unwrap(), operands .split_ascii_whitespace() .map(|operand| operand.parse().unwrap()) .collect(), ) }) .collect() }