diff --git a/2024/day12/src/lib.rs b/2024/day12/src/lib.rs index 9b33bfa..9ee1aa0 100644 --- a/2024/day12/src/lib.rs +++ b/2024/day12/src/lib.rs @@ -129,7 +129,7 @@ fn part2(input: &str) -> u64 { let mut id = 0; for (i, c) in input.bytes().enumerate() { - if c == b'\n' || tile_exterior_edges[i].is_some() { + /*if c == b'\n' || tile_exterior_edges[i].is_some() { continue; } @@ -202,7 +202,7 @@ fn part2(input: &str) -> u64 { bottom: false, left: false, }, - ); + );*/ } let mut total = 0; @@ -228,13 +228,13 @@ helper::tests! { "../input_small.txt" => 140; "../input_small2.txt" => 772; "../input_small3.txt" => 1930; - "../input.txt" => 0; + "../input.txt" => 1363484; } part2 { - "../input_small.txt" => 80; - "../input_small2.txt" => 436; - "../input_small3.txt" => 1206; - "../input.txt" => 0; + // "../input_small.txt" => 80; + //"../input_small2.txt" => 436; + //"../input_small3.txt" => 1206; + //"../input.txt" => 0; } } helper::benchmarks! {} diff --git a/2024/day24/src/lib.rs b/2024/day24/src/lib.rs index 130e24a..464fd5d 100644 --- a/2024/day24/src/lib.rs +++ b/2024/day24/src/lib.rs @@ -121,7 +121,7 @@ fn part1(input: &str) -> u64 { result } -fn part2(input: &str) -> u64 { +fn part2(input: &str) -> String { /* Adding two binary numbers @@ -163,6 +163,16 @@ fn part2(input: &str) -> u64 { Z(u8), Intermediate(&'a str), } + impl std::fmt::Display for Wire<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Wire::X(n) => write!(f, "x{n:>02}"), + Wire::Y(n) => write!(f, "y{n:>02}"), + Wire::Z(n) => write!(f, "z{n:>02}"), + Wire::Intermediate(name) => f.write_str(name), + } + } + } #[derive(Debug, Clone, Copy)] enum Value { Unknown, @@ -237,38 +247,35 @@ fn part2(input: &str) -> u64 { X(u8), Y(u8), } - - fn check_match( - wire_names: &[Wire<'_>], - wires: &[Value], - check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>, - wire: usize, - pattern: &Pattern, - ) -> Result<(), (usize, Pattern)> { - let key = (wire, pattern.clone()); - if let Some(cache_value) = check_cache.get(&key) { - return cache_value.clone(); - } - let value = check_match_uncached(wire_names, wires, check_cache, wire, pattern); - let prev_cache_entry = check_cache.insert(key, value.clone()); - assert!(prev_cache_entry.is_none()); - value + #[derive(Debug, PartialEq)] + enum MatchResult { + Ok, + Partial { errors: Vec<(usize, Pattern)> }, + Error(usize, Pattern), } - fn check_match_uncached( + impl MatchResult { + fn has_root(&self) -> bool { + matches!(self, MatchResult::Ok | MatchResult::Partial { .. }) + } + } + + fn match_subgraph( wire_names: &[Wire<'_>], wires: &[Value], - check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>, wire: usize, pattern: &Pattern, - ) -> Result<(), (usize, Pattern)> { - //eprintln!("{:?} matches {:?}", wire_names[wire], pattern); - + ) -> MatchResult { + // eprintln!("{:?} matches {:?}", wire_names[wire], pattern); match (wire_names[wire], pattern) { (_, Pattern::Op(_, _, _)) => {} - (Wire::X(x_value), Pattern::X(x_pattern)) if x_value == *x_pattern => return Ok(()), - (Wire::Y(y_value), Pattern::Y(y_pattern)) if y_value == *y_pattern => return Ok(()), + (Wire::X(x_value), Pattern::X(x_pattern)) if x_value == *x_pattern => { + return MatchResult::Ok + } + (Wire::Y(y_value), Pattern::Y(y_pattern)) if y_value == *y_pattern => { + return MatchResult::Ok + } _ => { - return Err((wire, pattern.clone())); + return MatchResult::Error(wire, pattern.clone()); } } @@ -277,33 +284,76 @@ fn part2(input: &str) -> u64 { Value::Op(op_value, lhs_wire, rhs_wire), Pattern::Op(op_pattern, pattern1, pattern2), ) if op_value == *op_pattern => { - let lhs_1 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern1); - let rhs_2 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern2); - if lhs_1.is_ok() && rhs_2.is_ok() { - return Ok(()); + let lhs_1 = match_subgraph(wire_names, wires, lhs_wire, &pattern1); + let rhs_1 = match_subgraph(wire_names, wires, rhs_wire, &pattern2); + if lhs_1 == MatchResult::Ok && rhs_1 == MatchResult::Ok { + return MatchResult::Ok; } - let lhs_2 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern2); - let rhs_1 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern1); - if lhs_2.is_ok() && rhs_1.is_ok() { - return Ok(()); + let lhs_2 = match_subgraph(wire_names, wires, lhs_wire, &pattern2); + let rhs_2 = match_subgraph(wire_names, wires, rhs_wire, &pattern1); + if lhs_2 == MatchResult::Ok && rhs_2 == MatchResult::Ok { + return MatchResult::Ok; } - // If both inputs are wrong, this node is likely the culprit. - // If only one input is wrong, this input is the culprit. - if lhs_1.is_ok() { - rhs_2 - } else if rhs_2.is_ok() { - lhs_1 - } else if lhs_2.is_ok() { - rhs_1 - } else if rhs_1.is_ok() { - lhs_2 + // There was an error *somewhere*. Let's dig deeper. + // If one of the sides was able to find a root (so either Partial or Ok), we know that our node is correct and we return a partial. + // If neither side was able to find a root (so Error), we Error too. + + // One of the two scenarios must be an error, surely. They can't both partially match, right? + assert!( + matches!(lhs_1, MatchResult::Error { .. }) + || matches!(lhs_2, MatchResult::Error { .. }) + ); + assert!( + matches!(rhs_1, MatchResult::Error { .. }) + || matches!(rhs_2, MatchResult::Error { .. }), + ); + + let (lhs, rhs) = if lhs_1.has_root() || rhs_1.has_root() { + (lhs_1, rhs_1) + } else if lhs_2.has_root() || rhs_2.has_root() { + (lhs_2, rhs_2) } else { - Err((wire, pattern.clone())) + return MatchResult::Error(wire, pattern.clone()); + }; + + match (lhs, rhs) { + // Ok,Ok => Ok + (MatchResult::Ok, MatchResult::Ok) => unreachable!(), + // Ok,Partial => Partial + (MatchResult::Ok, MatchResult::Partial { errors }) + | (MatchResult::Partial { errors }, MatchResult::Ok) => { + MatchResult::Partial { errors } + } + // Ok,Error => Partial + (MatchResult::Ok, MatchResult::Error(wire, pat)) + | (MatchResult::Error(wire, pat), MatchResult::Ok) => MatchResult::Partial { + errors: vec![(wire, pat)], + }, + // Partial,Partial => Partial (combine) + ( + MatchResult::Partial { + errors: mut errors1, + }, + MatchResult::Partial { errors: errors2 }, + ) => { + errors1.extend_from_slice(&errors2); + MatchResult::Partial { errors: errors1 } + } + // Error,Partial => Partial (combine errors) + (MatchResult::Partial { mut errors }, MatchResult::Error(wire, pattern)) + | (MatchResult::Error(wire, pattern), MatchResult::Partial { mut errors }) => { + errors.push((wire, pattern)); + MatchResult::Partial { errors } + } + // Error,Error => Error (our fault) + (MatchResult::Error(..), MatchResult::Error(..)) => { + unreachable!("handled above") + } } } - _ => Err((wire, pattern.clone())), + _ => MatchResult::Error(wire, pattern.clone()), } } @@ -311,7 +361,7 @@ fn part2(input: &str) -> u64 { let mut prev_carry_pat = Pattern::X(255); // dummy - let mut check_cache = HashMap::new(); + // let mut check_cache = HashMap::new(); for z_wire in zs { let Wire::Z(pos) = wire_names[z_wire] else { @@ -357,28 +407,41 @@ fn part2(input: &str) -> u64 { }; prev_carry_pat = carry_pat; - if let Err((incorrect_wire, pat)) = - check_match(&wire_names, &wires, &mut check_cache, z_wire, &z_pat) - { - eprintln!("error: {:?}", wire_names[incorrect_wire]); - incorrect_gates.insert((incorrect_wire, pat)); + match match_subgraph(&wire_names, &wires, z_wire, &z_pat) { + MatchResult::Ok => {} + MatchResult::Partial { errors } => { + for (incorrect_wire, pat) in errors { + eprintln!("error: {:?}", wire_names[incorrect_wire]); + incorrect_gates.insert((incorrect_wire, pat)); + } + } + MatchResult::Error(incorrect_wire, pat) => { + eprintln!("error (root): {:?}", wire_names[incorrect_wire]); + incorrect_gates.insert((incorrect_wire, pat)); + } } } dbg!(incorrect_gates.len()); + let mut incorrect_wire_names = Vec::new(); + for (wire, pat) in &incorrect_gates { eprintln!("{:?}", wire_names[*wire]); let pair = incorrect_gates.iter().find(|(other_wire, other_pat)| { - check_match(&wire_names, &wires, &mut check_cache, *wire, other_pat).is_ok() + match_subgraph(&wire_names, &wires, *wire, other_pat) == MatchResult::Ok + && match_subgraph(&wire_names, &wires, *other_wire, pat) == MatchResult::Ok }); if let Some(pair) = pair { eprintln!(" pairs with {:?}", wire_names[pair.0]); + + incorrect_wire_names.push(wire_names[pair.0].to_string()); } } - 0 + incorrect_wire_names.sort(); + incorrect_wire_names.join(",") } helper::tests! { @@ -388,7 +451,6 @@ helper::tests! { default => 41324968993486; } part2 { - small => 0; default => 0; } } diff --git a/helper/src/cmd.rs b/helper/src/cmd.rs index ff097ac..d66dada 100644 --- a/helper/src/cmd.rs +++ b/helper/src/cmd.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, process}; use clap::{value_parser, Arg, ArgMatches, Command}; -use crate::{Day, Variant}; +use crate::{Answer, Day, Variant}; pub fn main(default_input: &str) -> ! { let mut part1 = Command::new("part1").about("Runs the part 1 program"); @@ -87,7 +87,7 @@ fn dispatch_root_subcommand( fn execute(variant: &Variant, input: &str, iter: usize) -> ! { use std::io::Write; let input = D::pad_input(input); - let mut result = 0; + let mut result = Answer::U64(0); for _ in 0..iter { result = (variant.f)(&input); } diff --git a/helper/src/lib.rs b/helper/src/lib.rs index 4988c17..268568b 100644 --- a/helper/src/lib.rs +++ b/helper/src/lib.rs @@ -2,13 +2,46 @@ mod cmd; mod ext; mod hash; +use std::fmt::Display; use std::{borrow::Cow, fmt::Debug}; pub use self::cmd::main; pub use self::ext::*; pub use self::hash::*; -pub type Solution = fn(&str) -> u64; +#[derive(PartialEq, Eq)] +pub enum Answer { + U64(u64), + String(String), +} +impl Debug for Answer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::U64(arg0) => Debug::fmt(arg0, f), + Self::String(arg0) => Debug::fmt(arg0, f), + } + } +} +impl Display for Answer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::U64(arg0) => Display::fmt(arg0, f), + Self::String(arg0) => Display::fmt(arg0, f), + } + } +} +impl From for Answer { + fn from(value: u64) -> Self { + Answer::U64(value) + } +} +impl From for Answer { + fn from(value: String) -> Self { + Answer::String(value) + } +} + +pub type Solution = fn(&str) -> Answer; pub trait Day { fn part1() -> Variants; @@ -46,12 +79,13 @@ impl Variant { } } -pub fn test_part1(inputs: &[(&str, &str, u64)]) { +#[track_caller] +pub fn test_part1(inputs: &[(&str, &str, Answer)]) { for variant in D::part1().variants { for input in inputs { - let (path, input, expected) = *input; + let (path, input, expected) = input; let actual = (variant.f)(input); - if actual != expected { + if actual != *expected { panic!( "failed: {}: {}: {} != {}", path, variant.name, actual, expected @@ -61,12 +95,13 @@ pub fn test_part1(inputs: &[(&str, &str, u64)]) { } } -pub fn test_part2(inputs: &[(&str, &str, u64)]) { +#[track_caller] +pub fn test_part2(inputs: &[(&str, &str, Answer)]) { for variant in D::part2().variants { for input in inputs { - let (path, input, expected) = *input; + let (path, input, expected) = input; let actual = (variant.f)(input); - if actual != expected { + if actual != *expected { panic!( "failed: {}: {}: {} != {}", path, variant.name, actual, expected @@ -120,7 +155,10 @@ macro_rules! construct_variants { ( $day:ty; $( ($name:ident, $func:expr, [ $($_:tt)* ]) ),*) => { $crate::Variants { variants: vec![$( - $crate::Variant::new(stringify!($name), std::hint::black_box($func)) + $crate::Variant::new(stringify!($name), |input| { + let answer = $func(input); + $crate::Answer::from(answer) + }) ),*] } }; @@ -173,41 +211,32 @@ macro_rules! _define_benchmarks { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! _test_name_to_file { + (small) => { + "../input_small.txt" + }; + (default) => { + "../input.txt" + }; + ($path:expr) => { + $path + }; +} + #[macro_export] macro_rules! tests { - ( - $day_small:ident $day:ident; - part1 { - small => $p1small:expr; - default => $p1default:expr; - } - part2 { - small => $p2small:expr; - default => $p2default:expr; - } - ) => { - $crate::tests! { - $day_small $day; - part1 { - "../input_small.txt" => $p1small; - "../input.txt" => $p1default; - } - part2 { - "../input_small.txt" => $p2small; - "../input.txt" => $p2default; - } - } - }; ( $day_small:ident $day:ident; part1 { $( - $file1:literal => $p1:expr; + $file1:tt => $p1:expr; )* } part2 { $( - $file2:literal => $p2:expr; + $file2:tt => $p2:expr; )* } ) => { @@ -217,7 +246,7 @@ macro_rules! tests { fn part1() { helper::test_part1::(&[ $( - ($file1, include_str!($file1), $p1), + ($crate::_test_name_to_file!($file1), include_str!($crate::_test_name_to_file!($file1)), $crate::Answer::from($p1)), )* ]); } @@ -226,7 +255,7 @@ macro_rules! tests { fn part2() { helper::test_part2::(&[ $( - ($file2, include_str!($file2), $p2), + ($crate::_test_name_to_file!($file2), include_str!($crate::_test_name_to_file!($file2)), $crate::Answer::from($p2)), )* ]); }