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)]
|
2025-05-25 18:06:17 -05:00
|
|
|
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)]
|
2025-05-25 18:06:17 -05:00
|
|
|
enum TransitionDirection {
|
|
|
|
|
Asc, // level is increasing
|
|
|
|
|
Desc, // level is decreasing
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 19:27:15 -05:00
|
|
|
#[derive(Debug)]
|
2025-05-25 18:06:17 -05:00
|
|
|
struct LevelTransition {
|
|
|
|
|
state: TransitionState,
|
|
|
|
|
direction: TransitionDirection,
|
|
|
|
|
}
|
2025-05-25 19:36:45 -05:00
|
|
|
/// Given two level inputs, return a LevelTransition of the state:
|
|
|
|
|
/// - `TransitionState::Bad` if the difference between
|
2025-05-25 18:06:17 -05:00
|
|
|
/// 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
|
2025-05-25 18:06:17 -05:00
|
|
|
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;
|
|
|
|
|
|
2025-05-25 18:06:17 -05:00
|
|
|
// 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
|
2025-05-25 18:06:17 -05:00
|
|
|
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-25 18:06:17 -05:00
|
|
|
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
|
2025-05-25 18:06:17 -05:00
|
|
|
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
|
2025-05-25 18:06:17 -05:00
|
|
|
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
|
2025-05-25 18:06:17 -05:00
|
|
|
for level_transition in report[1..].iter() {
|
2025-05-25 17:15:31 -05:00
|
|
|
// If bad, then it's just bad
|
2025-05-25 18:06:17 -05:00
|
|
|
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
|
2025-05-25 18:06:17 -05:00
|
|
|
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
|
2025-05-25 18:06:17 -05:00
|
|
|
prev_transition = level_transition;
|
2025-05-25 17:15:31 -05:00
|
|
|
}
|
|
|
|
|
return true;
|
2025-05-25 16:02:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-05-25 19:27:15 -05:00
|
|
|
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)) => {
|
|
|
|
|
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
|
|
|
|
|
}
|
2025-05-25 17:15:31 -05:00
|
|
|
|
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);
|
|
|
|
|
|
2025-05-25 18:06:17 -05:00
|
|
|
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-25 19:27:15 -05:00
|
|
|
if is_report_safe_no_dampening(parsed_report.clone()) {
|
2025-05-25 18:06:17 -05:00
|
|
|
accumulator_no_dampening += 1;
|
2025-05-25 16:02:23 -05:00
|
|
|
}
|
2025-05-25 19:27:15 -05:00
|
|
|
if is_report_safe_with_dampening(parsed_report) {
|
|
|
|
|
accumulator_with_dampening += 1;
|
|
|
|
|
}
|
2025-05-25 16:02:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-05-25 19:27:15 -05:00
|
|
|
println!(
|
|
|
|
|
"test: {}",
|
|
|
|
|
is_report_safe_with_dampening(vec![1, 3, 2, 4, 5])
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-25 18:06:17 -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
|
|
|
}
|