// https://adventofcode.com/2024/day/2 // // run command: `cargo run ./input.txt` use std::env; use std::error::Error; use std::fs::File; use std::io::{BufReader, prelude::*}; use std::str::FromStr; /// Parse a report into a Vec fn parse_report(line: &str) -> Result, ::Err> where T: FromStr, { let parsed_line = line .split(char::is_whitespace) .map(|v| v.parse()) .collect::, _>>()?; Ok(parsed_line) } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum TransitionState { Good, // Good state Bad, // Bad state Dampened, // Could be removed to make a good state DampenedEnd, // Could be removed to make a good state, end or start of report } #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum TransitionDirection { Asc, // level is increasing Desc, // level is decreasing } #[derive(Debug)] struct LevelTransition { state: TransitionState, direction: TransitionDirection, } /// Given two level inputs, return a LevelTransition of the state: /// - `TransitionState::Bad` if the difference between /// the levels is either 0 or great than 3 /// - otherwise `TransitionState::Good` /// /// And direction: /// - `TransitionDirection::Asc` if the value from curr_level to next_level is increasing /// - `TransitionDirection::Desc` if the value from curr_level to next_level is decreasing fn compare_levels(curr_level: i64, next_level: i64) -> LevelTransition { let level_diff = next_level - curr_level; // If the diff is 0 or greater than 3, return bad state let state = if level_diff == 0 || level_diff.abs() > 3 { TransitionState::Bad } else { TransitionState::Good }; // If great than zero they are ascending let direction = if level_diff > 0 { TransitionDirection::Asc } else { TransitionDirection::Desc }; return LevelTransition { state, direction }; } fn is_report_safe_no_dampening(report: Vec) -> bool { // Turn the report into a bunch of states for each adjacent pair in the report let report: Vec = report .windows(2) .map(|l| compare_levels(l[0], l[1])) .collect(); // Setup the loop by checking the first start as a special case let mut prev_transition = &report[0]; if prev_transition.state != TransitionState::Good { return false; } // Check each state for level_transition in report[1..].iter() { // If bad, then it's just bad if level_transition.state != TransitionState::Good { return false; } // If the states aren't the same as the prev, it must // have changed direction if level_transition.direction != prev_transition.direction { return false; } // Setup for next iteration prev_transition = level_transition; } return true; } fn is_report_safe_with_dampening(report: Vec) -> bool { let mut modified_report: Vec> = vec![None]; let mut tmp_report: Vec> = report.iter().map(|v| Some(*v)).collect(); modified_report.append(&mut tmp_report); modified_report.push(None); let mut transitions = Vec::new(); for window in modified_report.windows(3) { let window_tuple = (window[0], window[1], window[2]); let state = match window_tuple { // We loop through in 3 value windows. We padded a None on // either side of the vec to detect the ends of the report for special handling. // // If the middle value from the window could be removed to make that section of repo SAFE, //mark as Dempened. We will use this later to determine if there's only a single dampened //value we can remove to meet the dampening criteria. (None, Some(curr), Some(next)) | (Some(curr), Some(next), None) => { // Matched first or last element, which means we only have one check to do // and we always return Dampened instead of Bad let local_transition = compare_levels(curr, next); if local_transition.state == TransitionState::Bad { // shadow bad with dampened since can always // remove the first and last element. LevelTransition { state: TransitionState::DampenedEnd, ..local_transition } } else { local_transition } } (Some(prev), Some(curr), Some(next)) => { let first_transition = compare_levels(prev, curr); let second_transition = compare_levels(curr, next); if first_transition.state != TransitionState::Good || second_transition.state != TransitionState::Good || first_transition.direction != second_transition.direction { let jump_transition = compare_levels(prev, next); if jump_transition.state == TransitionState::Good { //println!("Dampening value window: {:?}", window_tuple); // If we removed the middle value, we would be in a SAFE state, mark as DAMPENED LevelTransition { state: TransitionState::Dampened, ..jump_transition } } else { // Removing the middle value does not help us. mark as BAD LevelTransition { state: TransitionState::Bad, ..jump_transition } } } else { // Both transitions are good and moving in the same directions, so we can return either. second_transition } } _ => panic!("This should never happen!"), }; transitions.push(state); } let (mut dampened_cnt, mut prev_direction) = match transitions[0].state { TransitionState::Good => (0, transitions[0].direction), TransitionState::Dampened | TransitionState::DampenedEnd => (1, transitions[1].direction), TransitionState::Bad => { panic!("This should never happen!"); } }; for transition in &transitions { match transition.state { TransitionState::Good => { if transition.direction != prev_direction { println!( "Failed for non matching direction: {:?} -> {:?}", report, transitions ); return false; } } TransitionState::Dampened => { dampened_cnt += 1; if transition.direction != prev_direction { println!( "Failed for non matching direction: {:?} -> {:?}", report, transitions ); return false; } } TransitionState::DampenedEnd => { dampened_cnt += 1; } TransitionState::Bad => { // If it's marked as bad we can't fix it by dampening, return false return false; } } // We can only dampen one value if dampened_cnt > 1 { println!( "Failed for too much dampening: {:?} -> {:?}", report, transitions ); return false; } prev_direction = transition.direction; } true } 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_file = File::open(input_file_string)?; let input_reader = BufReader::new(input_file); let mut accumulator_no_dampening = 0; let mut accumulator_with_dampening = 0; for line in input_reader.lines() { let line = line?; let parsed_report: Vec = parse_report(&line)?; if is_report_safe_no_dampening(parsed_report.clone()) { accumulator_no_dampening += 1; } if is_report_safe_with_dampening(parsed_report) { accumulator_with_dampening += 1; } } println!( "test: {}", is_report_safe_with_dampening(vec![1, 3, 2, 4, 5]) ); println!( "Number of SAFE reports (No Dampening - Part 1): {}", accumulator_no_dampening ); println!( "Number of SAFE reports (With Dampening - Part 2): {}", accumulator_with_dampening ); Ok(()) }