// 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 }; } /// Tests if a report is safe 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; } /// Tests if a report is safe or code be made safe if you remove a single /// level from the report fn is_report_safe_with_dampening_brute_force(report: &Vec) -> bool { if is_report_safe_no_dampening(&report) { return true; } // Loop through removing a single level to test all dampening combinations for index in 0..report.len() { let sub_report = [&report[0..index], &report[index + 1..]].concat(); if is_report_safe_no_dampening(&sub_report) { return true; } } return false; } // fn compare_levels_3(prev_level: i64, curr_level: i64, next_level: i64) -> LevelTransition { // let first_transition = compare_levels(prev_level, curr_level); // let second_transition = compare_levels(curr_level, next_level); // if first_transition.state != TransitionState::Good // || second_transition.state != TransitionState::Good // || first_transition.direction != second_transition.direction // { // let jump_transition = compare_levels(prev_level, next_level); // 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 // } // } // fn is_report_safe_with_dampening(report: Vec) -> bool { // match report.len() { // 0..=2 => return true, // 3 => match compare_levels_3(report[0], report[1], report[2]).state { // TransitionState::Good | TransitionState::Dampened | TransitionState::DampenedEnd => { // return true; // } // TransitionState::Bad => return false, // }, // 4.. => { // let first_transition = compare_levels(report[0], report[1]); // let (prev_transition, dampen_cnt) = match first_transition.state { // TransitionState::Bad | TransitionState::Dampened | TransitionState::DampenedEnd => { // // Damnpen first level // let second_transition=compare_levels(report[1], report[2]); // if second_transition.state != TransitionState::Good { // // Already dampened // return false // } // (second_transition, 1) // }, // TransitionState::Good => { // let second_transition = compare_levels_3(report[0], report[1], report[2]); // }, // } // } // }; // } // 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)) => compare_levels_3(prev, curr, next), // _ => 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) { accumulator_no_dampening += 1; } if is_report_safe_with_dampening_brute_force(&parsed_report) { accumulator_with_dampening += 1; } } 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(()) }