//https://adventofcode.com/2024/day/3 // // Run command: cargo run ./input.txt use core::ops::Mul; use core::panic; use std::env; use std::error::Error; use std::fs::File; use std::io::{BufReader, prelude::*}; use std::iter::Peekable; use std::str::FromStr; #[derive(Eq, PartialEq, Clone, Debug)] enum Token { Text(String), Number(String), Comma, OpenParenthesis, CloseParenthesis, SpecialChar(char), Whitespace, NewLine, } fn tokenize(input_str: String) -> Vec { let mut tokens: Vec = Vec::new(); let mut chars = input_str.chars().peekable(); while let Some(c) = chars.next() { let token = match c { // Text Numeric _ if char::is_alphabetic(c) => { let mut text = c.to_string(); // HACK: including ' as part of alphabetic to implent "don't" expressions for part 2 while let Some(alphabetic_char) = chars.next_if(|p| p.is_alphabetic() || *p == '\'') { text.push(alphabetic_char) } Token::Text(text) } // Numbers _ if char::is_numeric(c) => { let mut number = c.to_string(); while let Some(numeric_char) = chars.next_if(|p| p.is_numeric()) { number.push(numeric_char) } Token::Number(number) } // whitespace _ if char::is_whitespace(c) => { while chars.next_if(|p| p.is_whitespace()).is_some() {} Token::Whitespace } _ if c == ',' => Token::Comma, _ if c == '(' => Token::OpenParenthesis, _ if c == ')' => Token::CloseParenthesis, _ if c == '\n' => Token::NewLine, _ => Token::SpecialChar(c), }; tokens.push(token); } tokens } #[derive(Debug)] struct MulOp { lhs: String, rhs: String, } impl MulOp { pub fn new(lhs: String, rhs: String) -> MulOp { MulOp { lhs, rhs } } pub fn multiple(&self) -> Result<::Output, ::Err> where T: FromStr + Mul, { let lhs: T = self.lhs.parse()?; let rhs: T = self.rhs.parse()?; Ok(lhs * rhs) } } fn is_mul_text_token(text_token: &str) -> bool { if text_token.ends_with("mul") { true } else { false } } fn parse_mul<'a>(token_iter: &mut Peekable>) -> Option { // Take OpenParenthesis or return None if !token_iter.next_if_eq(&&Token::OpenParenthesis).is_some() { return None; } // Take Number or return None let lhs = if let Some(Token::Number(lhs)) = token_iter.next_if(|v| matches!(v, Token::Number(_))) { lhs.to_owned() } else { return None; }; // Take Comma or return None if !token_iter.next_if_eq(&&Token::Comma).is_some() { return None; } // Take Number or return None let rhs = if let Some(Token::Number(rhs)) = token_iter.next_if(|v: &&Token| matches!(v, Token::Number(_))) { rhs.to_owned() } else { return None; }; // Take CloseParenthesis or return None if !token_iter.next_if_eq(&&Token::CloseParenthesis).is_some() { return None; } Some(MulOp::new(lhs, rhs)) } #[derive(Debug)] struct DoOp {} impl DoOp { pub fn new() -> DoOp { DoOp {} } } fn is_do_text_token(text_token: &str) -> bool { if text_token.ends_with("do") { true } else { false } } fn parse_do<'a>(token_iter: &mut Peekable>) -> Option { // Take OpenParenthesis or return None if !token_iter.next_if_eq(&&Token::OpenParenthesis).is_some() { return None; } // Take CloseParenthesis or return None if !token_iter.next_if_eq(&&Token::CloseParenthesis).is_some() { return None; } Some(DoOp::new()) } #[derive(Debug)] struct DontOp {} impl DontOp { pub fn new() -> DontOp { DontOp {} } } fn is_dont_text_token(text_token: &str) -> bool { if text_token.ends_with("don't") { true } else { false } } fn parse_dont<'a>(token_iter: &mut Peekable>) -> Option { // Take OpenParenthesis or return None if !token_iter.next_if_eq(&&Token::OpenParenthesis).is_some() { return None; } // Take CloseParenthesis or return None if !token_iter.next_if_eq(&&Token::CloseParenthesis).is_some() { return None; } Some(DontOp::new()) } #[derive(Debug)] enum Ops { Mul(MulOp), Do(DoOp), Dont(DontOp), } fn parse_tokens(tokens: &Vec) -> Vec { let mut tokens = tokens.iter().peekable(); let mut muls: Vec = Vec::new(); while let Some(token) = tokens.next() { match token { Token::Text(t) => { if is_mul_text_token(t) { let op = parse_mul(tokens.by_ref()); if let Some(op) = op { muls.push(Ops::Mul(op)); } } else if is_do_text_token(t) { let op = parse_do(tokens.by_ref()); if let Some(op) = op { muls.push(Ops::Do(op)); } } else if is_dont_text_token(t) { let op = parse_dont(tokens.by_ref()); if let Some(op) = op { muls.push(Ops::Dont(op)); } } } _ => (), } } muls } fn main() -> Result<(), Box> { // Handle command input let args: Vec = env::args().collect(); if args.len() != 2 { panic!( "{} must be run with a single argument of files name!", &args[0] ) } let input_file_string = &args[1]; let input_buffer = BufReader::new(File::open(input_file_string)?); let mut tokens: Vec = Vec::new(); for line in input_buffer.lines() { tokens.append(&mut tokenize(line? + "\n")); } //println!("{:?}", tokens); let muls = parse_tokens(&tokens); //println!("{:?}", muls); let mut accumulator_part1: i64 = 0; let mut accumulator_part2: i64 = 0; let mut enabled: bool = true; for op in muls { match op { Ops::Mul(mul_op) => { let value = mul_op.multiple::()?; accumulator_part1 += value; if enabled { accumulator_part2 += value; } } Ops::Do(_) => enabled = true, Ops::Dont(_) => enabled = false, } } println!("Answer part 1: {}", accumulator_part1); println!("Answer part 2: {}", accumulator_part2); Ok(()) }