This commit is contained in:
nora 2024-12-30 21:09:55 +01:00
parent eb8f0855bf
commit 3fb4bf282b
4 changed files with 189 additions and 98 deletions

View file

@ -129,7 +129,7 @@ fn part2(input: &str) -> u64 {
let mut id = 0; let mut id = 0;
for (i, c) in input.bytes().enumerate() { 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; continue;
} }
@ -202,7 +202,7 @@ fn part2(input: &str) -> u64 {
bottom: false, bottom: false,
left: false, left: false,
}, },
); );*/
} }
let mut total = 0; let mut total = 0;
@ -228,13 +228,13 @@ helper::tests! {
"../input_small.txt" => 140; "../input_small.txt" => 140;
"../input_small2.txt" => 772; "../input_small2.txt" => 772;
"../input_small3.txt" => 1930; "../input_small3.txt" => 1930;
"../input.txt" => 0; "../input.txt" => 1363484;
} }
part2 { part2 {
"../input_small.txt" => 80; // "../input_small.txt" => 80;
"../input_small2.txt" => 436; //"../input_small2.txt" => 436;
"../input_small3.txt" => 1206; //"../input_small3.txt" => 1206;
"../input.txt" => 0; //"../input.txt" => 0;
} }
} }
helper::benchmarks! {} helper::benchmarks! {}

View file

@ -121,7 +121,7 @@ fn part1(input: &str) -> u64 {
result result
} }
fn part2(input: &str) -> u64 { fn part2(input: &str) -> String {
/* /*
Adding two binary numbers Adding two binary numbers
@ -163,6 +163,16 @@ fn part2(input: &str) -> u64 {
Z(u8), Z(u8),
Intermediate(&'a str), 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)] #[derive(Debug, Clone, Copy)]
enum Value { enum Value {
Unknown, Unknown,
@ -237,38 +247,35 @@ fn part2(input: &str) -> u64 {
X(u8), X(u8),
Y(u8), Y(u8),
} }
#[derive(Debug, PartialEq)]
fn check_match( enum MatchResult {
wire_names: &[Wire<'_>], Ok,
wires: &[Value], Partial { errors: Vec<(usize, Pattern)> },
check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>, Error(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
} }
fn check_match_uncached( impl MatchResult {
fn has_root(&self) -> bool {
matches!(self, MatchResult::Ok | MatchResult::Partial { .. })
}
}
fn match_subgraph(
wire_names: &[Wire<'_>], wire_names: &[Wire<'_>],
wires: &[Value], wires: &[Value],
check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>,
wire: usize, wire: usize,
pattern: &Pattern, pattern: &Pattern,
) -> Result<(), (usize, Pattern)> { ) -> MatchResult {
//eprintln!("{:?} matches {:?}", wire_names[wire], pattern); // eprintln!("{:?} matches {:?}", wire_names[wire], pattern);
match (wire_names[wire], pattern) { match (wire_names[wire], pattern) {
(_, Pattern::Op(_, _, _)) => {} (_, Pattern::Op(_, _, _)) => {}
(Wire::X(x_value), Pattern::X(x_pattern)) if x_value == *x_pattern => return Ok(()), (Wire::X(x_value), Pattern::X(x_pattern)) if x_value == *x_pattern => {
(Wire::Y(y_value), Pattern::Y(y_pattern)) if y_value == *y_pattern => return Ok(()), 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), Value::Op(op_value, lhs_wire, rhs_wire),
Pattern::Op(op_pattern, pattern1, pattern2), Pattern::Op(op_pattern, pattern1, pattern2),
) if op_value == *op_pattern => { ) if op_value == *op_pattern => {
let lhs_1 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern1); let lhs_1 = match_subgraph(wire_names, wires, lhs_wire, &pattern1);
let rhs_2 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern2); let rhs_1 = match_subgraph(wire_names, wires, rhs_wire, &pattern2);
if lhs_1.is_ok() && rhs_2.is_ok() { if lhs_1 == MatchResult::Ok && rhs_1 == MatchResult::Ok {
return Ok(()); return MatchResult::Ok;
} }
let lhs_2 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern2); let lhs_2 = match_subgraph(wire_names, wires, lhs_wire, &pattern2);
let rhs_1 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern1); let rhs_2 = match_subgraph(wire_names, wires, rhs_wire, &pattern1);
if lhs_2.is_ok() && rhs_1.is_ok() { if lhs_2 == MatchResult::Ok && rhs_2 == MatchResult::Ok {
return Ok(()); return MatchResult::Ok;
} }
// If both inputs are wrong, this node is likely the culprit. // There was an error *somewhere*. Let's dig deeper.
// If only one input is wrong, this input is the culprit. // 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 lhs_1.is_ok() { // If neither side was able to find a root (so Error), we Error too.
rhs_2
} else if rhs_2.is_ok() { // One of the two scenarios must be an error, surely. They can't both partially match, right?
lhs_1 assert!(
} else if lhs_2.is_ok() { matches!(lhs_1, MatchResult::Error { .. })
rhs_1 || matches!(lhs_2, MatchResult::Error { .. })
} else if rhs_1.is_ok() { );
lhs_2 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 { } 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 prev_carry_pat = Pattern::X(255); // dummy
let mut check_cache = HashMap::new(); // let mut check_cache = HashMap::new();
for z_wire in zs { for z_wire in zs {
let Wire::Z(pos) = wire_names[z_wire] else { let Wire::Z(pos) = wire_names[z_wire] else {
@ -357,28 +407,41 @@ fn part2(input: &str) -> u64 {
}; };
prev_carry_pat = carry_pat; prev_carry_pat = carry_pat;
if let Err((incorrect_wire, pat)) = match match_subgraph(&wire_names, &wires, z_wire, &z_pat) {
check_match(&wire_names, &wires, &mut check_cache, z_wire, &z_pat) MatchResult::Ok => {}
{ MatchResult::Partial { errors } => {
eprintln!("error: {:?}", wire_names[incorrect_wire]); for (incorrect_wire, pat) in errors {
incorrect_gates.insert((incorrect_wire, pat)); 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()); dbg!(incorrect_gates.len());
let mut incorrect_wire_names = Vec::new();
for (wire, pat) in &incorrect_gates { for (wire, pat) in &incorrect_gates {
eprintln!("{:?}", wire_names[*wire]); eprintln!("{:?}", wire_names[*wire]);
let pair = incorrect_gates.iter().find(|(other_wire, other_pat)| { 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 { if let Some(pair) = pair {
eprintln!(" pairs with {:?}", wire_names[pair.0]); 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! { helper::tests! {
@ -388,7 +451,6 @@ helper::tests! {
default => 41324968993486; default => 41324968993486;
} }
part2 { part2 {
small => 0;
default => 0; default => 0;
} }
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, process};
use clap::{value_parser, Arg, ArgMatches, Command}; use clap::{value_parser, Arg, ArgMatches, Command};
use crate::{Day, Variant}; use crate::{Answer, Day, Variant};
pub fn main<D: Day>(default_input: &str) -> ! { pub fn main<D: Day>(default_input: &str) -> ! {
let mut part1 = Command::new("part1").about("Runs the part 1 program"); let mut part1 = Command::new("part1").about("Runs the part 1 program");
@ -87,7 +87,7 @@ fn dispatch_root_subcommand<D: Day>(
fn execute<D: Day>(variant: &Variant, input: &str, iter: usize) -> ! { fn execute<D: Day>(variant: &Variant, input: &str, iter: usize) -> ! {
use std::io::Write; use std::io::Write;
let input = D::pad_input(input); let input = D::pad_input(input);
let mut result = 0; let mut result = Answer::U64(0);
for _ in 0..iter { for _ in 0..iter {
result = (variant.f)(&input); result = (variant.f)(&input);
} }

View file

@ -2,13 +2,46 @@ mod cmd;
mod ext; mod ext;
mod hash; mod hash;
use std::fmt::Display;
use std::{borrow::Cow, fmt::Debug}; use std::{borrow::Cow, fmt::Debug};
pub use self::cmd::main; pub use self::cmd::main;
pub use self::ext::*; pub use self::ext::*;
pub use self::hash::*; 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<u64> for Answer {
fn from(value: u64) -> Self {
Answer::U64(value)
}
}
impl From<String> for Answer {
fn from(value: String) -> Self {
Answer::String(value)
}
}
pub type Solution = fn(&str) -> Answer;
pub trait Day { pub trait Day {
fn part1() -> Variants; fn part1() -> Variants;
@ -46,12 +79,13 @@ impl Variant {
} }
} }
pub fn test_part1<D: Day>(inputs: &[(&str, &str, u64)]) { #[track_caller]
pub fn test_part1<D: Day>(inputs: &[(&str, &str, Answer)]) {
for variant in D::part1().variants { for variant in D::part1().variants {
for input in inputs { for input in inputs {
let (path, input, expected) = *input; let (path, input, expected) = input;
let actual = (variant.f)(input); let actual = (variant.f)(input);
if actual != expected { if actual != *expected {
panic!( panic!(
"failed: {}: {}: {} != {}", "failed: {}: {}: {} != {}",
path, variant.name, actual, expected path, variant.name, actual, expected
@ -61,12 +95,13 @@ pub fn test_part1<D: Day>(inputs: &[(&str, &str, u64)]) {
} }
} }
pub fn test_part2<D: Day>(inputs: &[(&str, &str, u64)]) { #[track_caller]
pub fn test_part2<D: Day>(inputs: &[(&str, &str, Answer)]) {
for variant in D::part2().variants { for variant in D::part2().variants {
for input in inputs { for input in inputs {
let (path, input, expected) = *input; let (path, input, expected) = input;
let actual = (variant.f)(input); let actual = (variant.f)(input);
if actual != expected { if actual != *expected {
panic!( panic!(
"failed: {}: {}: {} != {}", "failed: {}: {}: {} != {}",
path, variant.name, actual, expected path, variant.name, actual, expected
@ -120,7 +155,10 @@ macro_rules! construct_variants {
( $day:ty; $( ($name:ident, $func:expr, [ $($_:tt)* ]) ),*) => { ( $day:ty; $( ($name:ident, $func:expr, [ $($_:tt)* ]) ),*) => {
$crate::Variants { $crate::Variants {
variants: vec![$( 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_export]
macro_rules! tests { 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; $day_small:ident $day:ident;
part1 { part1 {
$( $(
$file1:literal => $p1:expr; $file1:tt => $p1:expr;
)* )*
} }
part2 { part2 {
$( $(
$file2:literal => $p2:expr; $file2:tt => $p2:expr;
)* )*
} }
) => { ) => {
@ -217,7 +246,7 @@ macro_rules! tests {
fn part1() { fn part1() {
helper::test_part1::<super::$day>(&[ helper::test_part1::<super::$day>(&[
$( $(
($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() { fn part2() {
helper::test_part2::<super::$day>(&[ helper::test_part2::<super::$day>(&[
$( $(
($file2, include_str!($file2), $p2), ($crate::_test_name_to_file!($file2), include_str!($crate::_test_name_to_file!($file2)), $crate::Answer::from($p2)),
)* )*
]); ]);
} }