diff --git a/.gitignore b/.gitignore index 7305871..ebf87bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target perf.* aoc_cookie -input_big.txt \ No newline at end of file +input_big.txt +*.svg diff --git a/2024/day02/src/lib.rs b/2024/day02/src/lib.rs index f4ecb1c..0be8375 100644 --- a/2024/day02/src/lib.rs +++ b/2024/day02/src/lib.rs @@ -13,6 +13,7 @@ helper::define_variants! { } part2 { basic => crate::part2; + fast_parse => crate::part2_fast_parse; } } @@ -59,7 +60,7 @@ fn part2(input: &str) -> u64 { .map(parse_unwrap) .collect::>(); - let check_asc_desc = |forwards, smol, big| { + let check_asc_desc = |forwards, smol, big| { if forwards { smol < big && smol + 3 >= big } else { @@ -131,6 +132,111 @@ fn part2(input: &str) -> u64 { .count() as u64 } +fn part2_fast_parse(input: &str) -> u64 { + let mut levels = Vec::::new(); + + let mut count = 0; + + let mut input = input.as_bytes(); + while let Some(end) = input.iter().position(|b| *b == b'\n') { + let mut line = &input[..end]; + + fn parse_digit(input: &[u8]) -> (u64, &[u8]) { + let mut result = 0; + let mut i = 0; + while input.len() > i && input[i].is_ascii_digit() { + result *= 10; + result += (input[i] - b'0') as u64; + i += 1; + } + (result, &input[i..]) + } + + let mut level; + (level, line) = parse_digit(line); + levels.push(level); + + while line.len() > 0 { + line = &line[1..]; // space + (level, line) = parse_digit(line); + levels.push(level); + } + + // Calculate + let check_asc_desc = |forwards, smol, big| { + if forwards { + smol < big && smol + 3 >= big + } else { + big < smol && big + 3 >= smol + } + }; + + let check_direction = |forwards| { + let mut unsafe_transitions = vec![]; + for (i, ab) in levels.windows(2).enumerate() { + if !check_asc_desc(forwards, ab[0], ab[1]) { + unsafe_transitions.push(i); + } + } + match unsafe_transitions.len() { + 0 => true, + 1 => { + // 1 3 2 4 5 + // ^unsafe transition, index 1 + // either idx 1 needs to go, or idx 2 needs to go (in this case 1) + let trans = unsafe_transitions[0]; + if trans == 0 || trans == (levels.len() - 2) { + // It's the first or last element. + return true; + } + + // Let's see what happens if we drop the first element (3 in the example). + if check_asc_desc(forwards, levels[trans - 1], levels[trans + 1]) { + // Dropping the first elem works! + return true; + } + + // Let's see what happens if we drop the second element (2 in the example). + if check_asc_desc(forwards, levels[trans], levels[trans + 2]) { + // Dropping the secnd elem works! + return true; + } + + false + } + 2 => { + // 1 5 3 4 5 + // ^ ^ unsafe transitions, idx 0 and 1 + // If the two transitions are not adjacent, there's no hope. + let (trans0, trans1) = (unsafe_transitions[0], unsafe_transitions[1]); + if trans0.abs_diff(trans1) != 1 { + return false; + } + + // Let's try dropping the middle one. + let min = trans0.min(trans1); + if check_asc_desc(forwards, levels[min], levels[min + 2]) { + // Dropping it works! + return true; + } + + false + } + _ => false, + } + }; + + if check_direction(true) || check_direction(false) { + count += 1; + } + + levels.clear(); + input = &input[(end + 1)..]; + } + + count +} + helper::tests! { day02 Day02; part1 {