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;
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! {}

View file

@ -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),
}
#[derive(Debug, PartialEq)]
enum MatchResult {
Ok,
Partial { errors: Vec<(usize, Pattern)> },
Error(usize, Pattern),
}
impl MatchResult {
fn has_root(&self) -> bool {
matches!(self, MatchResult::Ok | MatchResult::Partial { .. })
}
}
fn check_match(
fn match_subgraph(
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
}
fn check_match_uncached(
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)
{
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;
}
}

View file

@ -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<D: Day>(default_input: &str) -> ! {
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) -> ! {
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);
}

View file

@ -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<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 {
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 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<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 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::<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() {
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)),
)*
]);
}