diff --git a/.gitignore b/.gitignore index 29a05f3..a0ed07c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ mem.mem # Nice one rustc rustc-ice* +*.dot diff --git a/Cargo.lock b/Cargo.lock index 6069040..e19bd93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,14 +42,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", + "once_cell", + "unicode-width", "windows-sys", ] @@ -86,9 +86,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "indicatif" @@ -99,31 +99,25 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width", "web-time", ] [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" @@ -231,9 +225,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", @@ -246,12 +240,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.0" @@ -264,9 +252,9 @@ version = "0.1.0" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -275,13 +263,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -290,9 +277,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -300,9 +287,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -313,9 +300,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-time" @@ -329,9 +316,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] diff --git a/years/2024/src/day_24.rs b/years/2024/src/day_24.rs index a693a7b..b147643 100644 --- a/years/2024/src/day_24.rs +++ b/years/2024/src/day_24.rs @@ -1,17 +1,307 @@ +use std::collections::{HashMap, HashSet, VecDeque}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; pub struct Day24; +pub type Wires = HashMap; +pub type Gates = HashMap<(String, String, String), Gate>; + +fn find_gate<'a>(gates: &'a Gates, lhs: &str, rhs: &str, op: Op) -> Option<&'a Gate> { + gates.get(&(lhs.to_string(), rhs.to_string(), format!("{op:?}"))) +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Op { + And, + Or, + Xor, +} + +impl Op { + fn eval(&self, lhs: bool, rhs: bool) -> bool { + match self { + Self::And => lhs & rhs, + Self::Or => lhs | rhs, + Self::Xor => lhs ^ rhs, + } + } +} + +#[derive(Debug, Clone)] +pub struct Gate { + lhs: String, + op: Op, + rhs: String, + target: String, +} + +impl Gate { + pub fn parse(raw: &str) -> Self { + let mut s = raw.split(" "); + let lhs = s.next().unwrap().to_string(); + let op = s.next().unwrap(); + let rhs = s.next().unwrap().to_string(); + let target = s.skip(1).next().unwrap().to_string(); + let op = match op { + "AND" => Op::And, + "OR" => Op::Or, + "XOR" => Op::Xor, + _ => panic!(), + }; + Self { + lhs, + rhs, + target, + op, + } + } + + pub fn run(&self, wires: &mut Wires) -> bool { + if let (Some(&lhs), Some(&rhs)) = (wires.get(&self.lhs), wires.get(&self.rhs)) { + wires.insert(self.target.clone(), self.op.eval(lhs, rhs)); + true + } else { + false + } + } +} + +enum AdderTestResult { + Okay(String), + SwapNeeded(String, String), + CompletelyWrong, + End, +} + +impl AdderTestResult { + fn unwrap_okay(self) -> String { + match self { + Self::Okay(s) => s, + _ => panic!(), + } + } +} + +// The first adder is a half adder and needs to follow this format: +// +// x00, y00 -> AND -> [carry output wire] +// x00, y00 -> XOR -> z00 +// +fn test_first_adder(gates: &Gates) -> AdderTestResult { + let x0 = "x00".to_string(); + let y0 = "y00".to_string(); + if let Some(Gate { target, .. }) = find_gate(gates, &x0, &y0, Op::And) { + if let Some(Gate { + target: z_target, .. + }) = find_gate(gates, &x0, &y0, Op::Xor) + { + if z_target == "z00" { + if target == "z01" { + AdderTestResult::End + } else { + AdderTestResult::Okay(target.to_string()) + } + } else { + AdderTestResult::SwapNeeded(target.to_string(), z_target.to_string()) + } + } else { + AdderTestResult::CompletelyWrong + } + } else { + AdderTestResult::CompletelyWrong + } +} + +// Full adders must follow this pattern: +// +// 1. x[], y[] -> AND -> [xy_and_target] +// 2. x[], y[] -> XOR -> [xy_xor_target] +// 3. [carry_input], [xy_xor_target] -> XOR -> z[] +// 4. [carry_input], [xy_xor_target] -> AND -> [carry_xy_target] +// 5. [carry_xy_target], [xy_and_target] -> OR -> [carry_output] +// +// Of the gates, the ones that can swap outputs without creating a loop are as follows: +// A. #1 & #2 +// B. #3 & #5 +// C. #3 & #4 +// D. #1 & #3 +// +// These ones would result in insane or same output: +// #1 & #4, this actually results in the same output no matter what +// #2 & #3, this will result in #3's own output be its input which is invalid to this problem +// #2 & #4, ^ +// #2 & #5, ^ +// #4 & #5, ^ +// +fn test_adder(num: usize, carry_input: &String, gates: &Gates) -> AdderTestResult { + let x = format!("x{num:02}"); + let y = format!("y{num:02}"); + let z = format!("z{num:02}"); + if let ( + Some(Gate { + target: xy_and_target, + .. + }), + Some(Gate { + target: xy_xor_target, + .. + }), + ) = ( + find_gate(gates, &x, &y, Op::And), + find_gate(gates, &x, &y, Op::Xor), + ) { + if let Some(Gate { + target: z_target, .. + }) = find_gate(gates, &carry_input, xy_xor_target.as_str(), Op::Xor) + { + if *z_target != z { + // We know gate #3 is pointing to the wrong target, as it should be pointing to z[] + // We can now confidently swap z[] and this target + // This covers invalid state B, C, and D + return AdderTestResult::SwapNeeded(z_target.to_string(), z); + } + } else { + // We know that gate #2 has an invalid output, and since the only case that involves #2 + // is case A, we know that we're swapped with gate #1, we simply need to return the two + // targets we already have + return AdderTestResult::SwapNeeded( + xy_and_target.to_string(), + xy_xor_target.to_string(), + ); + }; + + // From here we've checked all test cases, we can confidently attempt to find the carry + // output now + let carry_xy_target = &find_gate(&gates, xy_xor_target, &carry_input, Op::And) + .expect("Failed to find carry_xy_target") + .target; + let carry_out = &find_gate(&gates, carry_xy_target, xy_and_target, Op::Or) + .expect("Failed to find carry_out") + .target; + if *carry_out == format!("z{:02}", num + 1) { + AdderTestResult::End + } else { + AdderTestResult::Okay(carry_out.to_string()) + } + } else { + AdderTestResult::CompletelyWrong + } +} + +fn swap_outputs(gates: &mut Gates, out1: &String, out2: &String) { + gates.values_mut().for_each(|g| { + if g.target == *out1 { + g.target = out2.to_string(); + } else if g.target == *out2 { + g.target = out1.to_string(); + } + }); +} + +// 0,1, +// z16,tdv,hnd,z09,z23,bks,nrn,tjp + impl Day for Day24 { + day_stuff!(24, "", "", (Wires, Gates)); + + fn part_1((mut wires, gates): Self::Input) -> Option { + let mut all_zs = gates + .values() + .filter(|g| g.target.starts_with('z')) + .map(|g| &g.target) + .collect::>(); + all_zs.sort(); + + let mut current_zs = HashSet::<&String>::with_capacity(all_zs.len()); + + let mut queue = gates.values().collect::>(); - day_stuff!(24, "", ""); + while let Some(gate) = queue.pop_front() + && current_zs.len() < all_zs.len() + { + if gate.run(&mut wires) { + if gate.target.starts_with('z') { + current_zs.insert(&gate.target); + } + } else { + queue.push_back(gate); + } + } - fn part_1(_input: Self::Input) -> Option { - None + let ans = all_zs.into_iter().enumerate().fold(0_usize, |acc, (i, z)| { + let wire = wires.get(z).unwrap(); + if *wire { + acc | (1 << i) + } else { + acc + } + }); + + Some(ans.to_string()) + } + + fn part_2((_, mut gates): Self::Input) -> Option { + let mut swapped = Vec::with_capacity(8); + let mut current_carry = String::new(); + + for i in 0.. { + let res = if i == 0 { + test_first_adder(&gates) + } else { + test_adder(i, ¤t_carry, &gates) + }; + + match res { + AdderTestResult::Okay(new_carry) => { + current_carry = new_carry; + } + AdderTestResult::End => { + break; + } + AdderTestResult::CompletelyWrong => { + panic!("Wrong adder"); + } + AdderTestResult::SwapNeeded(l, r) => { + swap_outputs(&mut gates, &l, &r); + current_carry = test_adder(i, ¤t_carry, &gates).unwrap_okay(); + swapped.push(l); + swapped.push(r); + } + }; + } + + swapped.sort(); + + Some(swapped.join(",")) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + let (inits, gates) = input.trim().split_once("\n\n").unwrap(); + + let wires = inits + .lines() + .map(|l| { + let (name, val) = l.split_once(": ").unwrap(); + (name.to_string(), val == "1") + }) + .collect::>(); + + let gates = gates + .lines() + .flat_map(|l| { + let gate = Gate::parse(l); + let op_str = format!("{:?}", gate.op); + [ + ( + (gate.lhs.clone(), gate.rhs.clone(), op_str.clone()), + gate.clone(), + ), + ((gate.rhs.clone(), gate.lhs.clone(), op_str), gate), + ] + }) + .collect::>(); + + (wires, gates) } } diff --git a/years/2024/src/day_25.rs b/years/2024/src/day_25.rs index 3a1fb9a..c32f8e3 100644 --- a/years/2024/src/day_25.rs +++ b/years/2024/src/day_25.rs @@ -1,17 +1,55 @@ +use std::collections::HashSet; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{tiles, upos}; pub struct Day25; +tiles!(Tile, [ + '#' => Fill, + '.' => Empty, +]); + +type Grid = utils::grid::Grid; + impl Day for Day25 { + day_stuff!(25, "", "", (HashSet<[u8; 5]>, HashSet<[u8; 5]>)); - day_stuff!(25, "", ""); + fn part_1((locks, keys): Self::Input) -> Option { + let ans = locks + .into_iter() + .flat_map(|l| { + keys.iter() + .filter(move |k| l.iter().zip(k.iter()).all(|(l, k)| *k <= (5 - *l))) + }) + .count(); - fn part_1(_input: Self::Input) -> Option { - None + Some(ans.to_string()) } fn part_2(_input: Self::Input) -> Option { - None + Some("🥳".to_string()) + } + + fn parse_input(input: &str) -> Self::Input { + let mut locks = HashSet::new(); + let mut keys = HashSet::new(); + + for grid in input.trim().split("\n\n").map(Grid::parse) { + let code = grid + .iter_cols() + .map(|col| (col.filter(|t| **t == Tile::Fill).count() - 1) as u8) + .collect::>(); + + let code = [code[0], code[1], code[2], code[3], code[4]]; + + if grid.get(upos!(0, 0)).is_some_and(|t| *t == Tile::Fill) { + locks.insert(code); + } else { + keys.insert(code); + } + } + + (locks, keys) } }