Files
adventofcode2024/day_02/src/main.rs

303 lines
11 KiB
Rust
Raw Normal View History

2025-05-25 17:15:31 -05:00
// https://adventofcode.com/2024/day/2
//
// run command: `cargo run ./input.txt`
2025-05-25 16:02:23 -05:00
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<T>
fn parse_report<T>(line: &str) -> Result<Vec<T>, <T as FromStr>::Err>
where
T: FromStr,
{
let parsed_line = line
.split(char::is_whitespace)
.map(|v| v.parse())
.collect::<Result<Vec<T>, _>>()?;
Ok(parsed_line)
}
2025-05-25 19:27:15 -05:00
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
enum TransitionState {
2025-05-25 19:27:15 -05:00
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
2025-05-25 16:02:23 -05:00
}
2025-05-25 19:27:15 -05:00
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
enum TransitionDirection {
Asc, // level is increasing
Desc, // level is decreasing
}
2025-05-25 19:27:15 -05:00
#[derive(Debug)]
struct LevelTransition {
state: TransitionState,
direction: TransitionDirection,
}
2025-05-26 08:06:54 -05:00
2025-05-25 19:36:45 -05:00
/// 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
2025-05-25 19:36:45 -05:00
/// - 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 {
2025-05-25 17:15:31 -05:00
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
};
2025-05-25 17:15:31 -05:00
// If great than zero they are ascending
let direction = if level_diff > 0 {
TransitionDirection::Asc
} else {
TransitionDirection::Desc
};
return LevelTransition { state, direction };
2025-05-25 17:15:31 -05:00
}
2025-05-26 08:06:54 -05:00
/// Tests if a report is safe
fn is_report_safe_no_dampening(report: &Vec<i64>) -> bool {
2025-05-25 17:15:31 -05:00
// Turn the report into a bunch of states for each adjacent pair in the report
let report: Vec<LevelTransition> = report
2025-05-25 17:15:31 -05:00
.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 {
2025-05-25 17:15:31 -05:00
return false;
}
// Check each state
for level_transition in report[1..].iter() {
2025-05-25 17:15:31 -05:00
// If bad, then it's just bad
if level_transition.state != TransitionState::Good {
2025-05-25 17:15:31 -05:00
return false;
2025-05-25 16:02:23 -05:00
}
2025-05-25 17:15:31 -05:00
// If the states aren't the same as the prev, it must
// have changed direction
if level_transition.direction != prev_transition.direction {
2025-05-25 17:15:31 -05:00
return false;
2025-05-25 16:02:23 -05:00
}
2025-05-25 17:15:31 -05:00
// Setup for next iteration
prev_transition = level_transition;
2025-05-25 17:15:31 -05:00
}
return true;
2025-05-25 16:02:23 -05:00
}
2025-05-26 08:06:54 -05:00
/// 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<i64>) -> bool {
if is_report_safe_no_dampening(&report) {
return true;
2025-05-25 19:27:15 -05:00
}
2025-05-26 08:06:54 -05:00
// 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;
2025-05-25 19:27:15 -05:00
}
}
2025-05-26 08:06:54 -05:00
return false;
2025-05-25 19:27:15 -05:00
}
2025-05-25 17:15:31 -05:00
2025-05-26 08:06:54 -05:00
// 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<i64>) -> 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<i64>) -> bool {
// let mut modified_report: Vec<Option<i64>> = vec![None];
// let mut tmp_report: Vec<Option<i64>> = 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
// }
2025-05-25 16:02:23 -05:00
fn main() -> Result<(), Box<dyn Error>> {
// Handle command input
let args: Vec<String> = 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;
2025-05-25 19:27:15 -05:00
let mut accumulator_with_dampening = 0;
2025-05-25 16:02:23 -05:00
for line in input_reader.lines() {
let line = line?;
let parsed_report: Vec<i64> = parse_report(&line)?;
2025-05-26 08:06:54 -05:00
if is_report_safe_no_dampening(&parsed_report) {
accumulator_no_dampening += 1;
2025-05-25 16:02:23 -05:00
}
2025-05-26 08:06:54 -05:00
if is_report_safe_with_dampening_brute_force(&parsed_report) {
2025-05-25 19:27:15 -05:00
accumulator_with_dampening += 1;
}
2025-05-25 16:02:23 -05:00
}
println!(
"Number of SAFE reports (No Dampening - Part 1): {}",
accumulator_no_dampening
);
2025-05-25 19:27:15 -05:00
println!(
"Number of SAFE reports (With Dampening - Part 2): {}",
accumulator_with_dampening
);
2025-05-25 16:02:23 -05:00
Ok(())
2025-05-25 13:09:21 -05:00
}