From 8af887ad9ebbbe84e04848584a88bcdc8ae9a822 Mon Sep 17 00:00:00 2001 From: Ben C Date: Sun, 22 Dec 2024 01:11:15 -0500 Subject: [PATCH] More days, I haven't committed in a while --- .gitignore | 4 + Cargo.lock | 52 ++++++++++ utils/src/num.rs | 2 +- years/2024/Cargo.toml | 1 + years/2024/src/day_14.rs | 26 ++--- years/2024/src/day_15.rs | 154 +++++++++++++++++++++++++++-- years/2024/src/day_16.rs | 190 ++++++++++++++++++++++++++++++++++-- years/2024/src/day_17.rs | 205 +++++++++++++++++++++++++++++++++++++-- years/2024/src/day_18.rs | 127 +++++++++++++++++++++++- years/2024/src/day_19.rs | 64 ++++++++++-- years/2024/src/day_20.rs | 94 ++++++++++++++++-- years/2024/src/day_21.rs | 157 ++++++++++++++++++++++++++++-- years/2024/src/day_22.rs | 83 ++++++++++++++-- 13 files changed, 1093 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 78f0a78..29a05f3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ result # For 2024 Day 14 - I am in hell trees*.txt +mem.mem + +# Nice one rustc +rustc-ice* diff --git a/Cargo.lock b/Cargo.lock index 1538659..6069040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,37 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -149,6 +180,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" @@ -355,6 +406,7 @@ version = "0.1.0" dependencies = [ "advent_core", "macros", + "rayon", "regex", "utils", ] diff --git a/utils/src/num.rs b/utils/src/num.rs index 8d126c6..35dc708 100644 --- a/utils/src/num.rs +++ b/utils/src/num.rs @@ -29,7 +29,7 @@ pub fn num_digits(num: usize) -> usize { /// Split a given number at a specific digit, this digit will be included in the right-hand side /// and excluded in the left. /// -/// If the split is invalid, zero may be returnd on either side of the result. +/// If the split is invalid, zero may be returned on either side of the result. /// /// # Examples /// diff --git a/years/2024/Cargo.toml b/years/2024/Cargo.toml index 22dfdbb..069ed06 100644 --- a/years/2024/Cargo.toml +++ b/years/2024/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] advent_core = { path = "../../advent_core" } macros = { path = "../../macros" } +rayon = "1.10.0" regex = "1.11.1" utils = { path = "../../utils" } diff --git a/years/2024/src/day_14.rs b/years/2024/src/day_14.rs index 9d4a63b..e453713 100644 --- a/years/2024/src/day_14.rs +++ b/years/2024/src/day_14.rs @@ -2,36 +2,30 @@ use std::{cmp::Ordering, collections::HashSet}; use advent_core::{day_stuff, ex_for_day, Day}; use regex::Regex; -use utils::{ipos, pos::Position}; +use utils::{pos::Position, upos}; pub struct Day14; -fn robot_go(pos: Position, vel: Position, times: isize, bounds: Position) -> Position { - let new_pos = pos.add(&vel.multiply_comp(times)); - let x_r = new_pos.x % bounds.x; - let y_r = new_pos.y % bounds.y; - ipos!( - if x_r < 0 { x_r + bounds.x } else { x_r }, - if y_r < 0 { y_r + bounds.y } else { y_r } - ) +fn robot_go(pos: Position, vel: Position, times: isize, bounds: (usize, usize)) -> Position { + pos.add(&vel.multiply_comp(times)).bind(bounds).into() } impl Day for Day14 { day_stuff!(14, "", "", Vec<(Position, Position)>); fn part_1(input: Self::Input) -> Option { - let bounds = Position::new(101, 103); + let bounds = (101, 103); let times = 100; let (ur, ul, ll, lr) = input .into_iter() .map(move |(pos, vel)| robot_go(pos, vel, times, bounds)) .fold((0, 0, 0, 0), move |mut acc, robo| { - let is_upper = match robo.y.cmp(&(bounds.y / 2)) { + let is_upper = match robo.y.cmp(&(bounds.1 as isize / 2)) { Ordering::Equal => None, Ordering::Greater => Some(false), Ordering::Less => Some(true), }; - let is_left = match robo.x.cmp(&(bounds.x / 2)) { + let is_left = match robo.x.cmp(&(bounds.0 as isize / 2)) { Ordering::Equal => None, Ordering::Greater => Some(false), Ordering::Less => Some(true), @@ -53,7 +47,7 @@ impl Day for Day14 { } fn part_2(input: Self::Input) -> Option { - let bounds = Position::new(101, 103); + let bounds = (101, 103); let re = Regex::new(include_str!("da_tree.txt")).unwrap(); @@ -63,12 +57,12 @@ impl Day for Day14 { .map(move |r| robot_go(r.0, r.1, i as isize, bounds)) .collect::>(); - let hay = (0..bounds.y) + let hay = (0..bounds.1) .flat_map(|y| { let bots = &bots; - (0..bounds.x) + (0..bounds.0) .map(move |x| { - let pos = Position::new(x, y); + let pos = upos!(x, y); if bots.contains(&pos) { 'X' } else { diff --git a/years/2024/src/day_15.rs b/years/2024/src/day_15.rs index 319a448..c74132a 100644 --- a/years/2024/src/day_15.rs +++ b/years/2024/src/day_15.rs @@ -1,17 +1,159 @@ +use std::collections::{HashMap, HashSet}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{ + dir::{Direction, Movement}, + grid::Grid, + pos::Position, + tiles, upos, +}; pub struct Day15; +tiles!(Tile, [ + '.' => Empty, + '@' => Robot, + '#' => Wall, + 'O' => Box, + '[' => BoxLeft, + ']' => BoxRight, +]); + +fn parse_instruction(c: char) -> Direction { + match c { + '^' => Direction::North, + '<' => Direction::West, + '>' => Direction::East, + 'v' => Direction::South, + _ => unreachable!("For {c:?}"), + } +} + +type PosMap = HashMap; + +fn movement(robo: Position, dir: Direction, map: &mut PosMap) -> Option { + let mut next_pos = robo.add(&dir.get_kernel()); + let mut to_update = Vec::with_capacity(20); + loop { + let next_tile = map.get(&next_pos).unwrap(); + if *next_tile == Tile::Wall { + return None; + } else if *next_tile == Tile::Box { + to_update.push(next_pos); + next_pos = next_pos.add(&dir.get_kernel()); + } else { + // Is Empty + to_update.push(next_pos); + break; + } + } + assert!(!to_update.is_empty()); + let first = to_update.first().unwrap(); + *map.get_mut(first).unwrap() = Tile::Robot; + *map.get_mut(&robo).unwrap() = Tile::Empty; + if to_update.len() >= 2 { + *map.get_mut(to_update.last().unwrap()).unwrap() = Tile::Box; + } + Some(*first) +} + +fn movement_pt_2(robo: Position, dir: Direction, map: &mut PosMap) -> Option { + let kern = dir.get_kernel(); + let mut to_check = HashSet::from_iter([robo.add(&kern)]); + let mut to_update = Vec::with_capacity(40); + to_update.push((robo.add(&kern), Tile::Robot)); + while !to_check.is_empty() { + let mut new_check = HashSet::new(); + for check in to_check.into_iter() { + let new_tile = *map.get(&check).unwrap(); + if new_tile == Tile::Wall { + return None; + } else if new_tile == Tile::BoxLeft && !dir.is_horizontal() { + let my_new_pos = check.add(&kern); + let r_pos = check.add(&upos!(1, 0)); + let r_new_pos = r_pos.add(&kern); + to_update.push((my_new_pos, Tile::BoxLeft)); + to_update.push((r_new_pos, Tile::BoxRight)); + new_check.insert(my_new_pos); + new_check.insert(r_new_pos); + } else if new_tile == Tile::BoxRight && !dir.is_horizontal() { + let my_new_pos = check.add(&kern); + let l_pos = check.sub(&upos!(1, 0)); + let l_new_pos = l_pos.add(&kern); + to_update.push((my_new_pos, Tile::BoxRight)); + to_update.push((l_new_pos, Tile::BoxLeft)); + new_check.insert(my_new_pos); + new_check.insert(l_new_pos); + } else if new_tile != Tile::Empty { + to_update.push((check.add(&kern), new_tile)); + new_check.insert(check.add(&kern)); + } + } + to_check = new_check; + } + for (pos, tile) in to_update.into_iter().rev() { + *map.get_mut(&pos).unwrap() = tile; + *map.get_mut(&pos.sub(&kern)).unwrap() = Tile::Empty; + } + Some(robo.add(&kern)) +} + +fn gps(pos_map: &PosMap) -> usize { + pos_map + .iter() + .filter_map(|(pos, tile)| { + if matches!(*tile, Tile::Box | Tile::BoxLeft) { + Some(100 * pos.y as usize + pos.x as usize) + } else { + None + } + }) + .sum() +} + impl Day for Day15 { + day_stuff!(15, "", "", (Position, PosMap, Vec)); - day_stuff!(15, "", ""); + fn part_1(input: Self::Input) -> Option { + let (mut robo, mut pos_map, ins) = input; + for i in ins { + if let Some(new_pos) = movement(robo, i, dbg!(&mut pos_map)) { + robo = new_pos; + } + } + Some(gps(&pos_map).to_string()) + } - fn part_1(_input: Self::Input) -> Option { - None + fn part_2(input: Self::Input) -> Option { + let (mut robo, mut pos_map, ins) = input; + for i in ins { + if let Some(new_pos) = movement_pt_2(robo, i, &mut pos_map) { + println!("Move success"); + robo = new_pos; + } + } + Some(gps(&pos_map).to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + // TODO: Temp for pt 2 + let replace = input.replace('#', "##"); + let replace = replace.replace('.', ".."); + let replace = replace.replace('O', "[]"); + let replace = replace.replace('@', "@."); + println!("{}", &replace); + let (map, dirs) = &replace.trim().split_once("\n\n").unwrap(); + let dirs = dirs + .split('\n') + .flat_map(|l| l.chars().map(parse_instruction)) + .collect(); + + let grid = Grid::::parse(map); + + let robo = grid.find_tile(&Tile::Robot).unwrap(); + + let pos_map = grid.iter().map(|(pos, tile)| (pos, *tile)).collect(); + + (robo, pos_map, dirs) } } diff --git a/years/2024/src/day_16.rs b/years/2024/src/day_16.rs index f453af8..1cbcbbb 100644 --- a/years/2024/src/day_16.rs +++ b/years/2024/src/day_16.rs @@ -1,17 +1,195 @@ +use std::{ + cmp::Ordering, + collections::{BinaryHeap, HashMap, HashSet}, +}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{ + dir::{Direction, Movement}, + pos::Position, + tiles, +}; pub struct Day16; +tiles!(Tile, [ + '.' => Open, + '#' => Wall, + 'S' => Start, + 'E' => End, +]); + +type Grid = utils::grid::Grid; + +#[derive(Clone, Eq, PartialEq)] +struct DState { + cost: usize, + vert: (Position, Direction), + prev: Vec, +} + +impl Ord for DState { + fn cmp(&self, other: &Self) -> Ordering { + other.cost.cmp(&self.cost) + } +} + +impl PartialOrd for DState { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl Day for Day16 { + day_stuff!(16, "", "", Grid); + + fn part_1(input: Self::Input) -> Option { + let start_pos = input.find_tile(&Tile::Start).unwrap(); + let end_pos = input.find_tile(&Tile::End).unwrap(); + + let mut queue = BinaryHeap::new(); + + queue.push(DState { + vert: (start_pos, Direction::East), + cost: 0, + prev: vec![], + }); + + let mut visited = HashMap::<(Position, Direction), usize>::new(); + + while let Some(DState { + vert: (pos, dir), + cost, + prev: _, + }) = queue.pop() + { + if pos == end_pos { + return Some(cost.to_string()); + } + + if visited + .get(&(pos, dir)) + .is_some_and(|min_score| *min_score < cost) + { + continue; + } + + for (next_dir, score) in input + .relatives(pos, &[dir, dir.ninety_deg(true), dir.ninety_deg(false)]) + .filter_map(|(next_dir, _, t)| { + if *t != Tile::Wall { + Some((next_dir, if next_dir == dir { 1 } else { 1000 })) + } else { + None + } + }) + { + let next_state = DState { + cost: cost + score, + vert: ( + if dir == next_dir { + pos.add(&dir.get_kernel()) + } else { + pos + }, + next_dir, + ), + prev: vec![], + }; + + if next_state.cost < *visited.get(&next_state.vert).unwrap_or(&usize::MAX) { + *visited.entry(next_state.vert).or_insert(usize::MAX) = next_state.cost; + queue.push(next_state); + } + } + } + + panic!("No Solution!!!") + } + + fn part_2(input: Self::Input) -> Option { + let start_pos = input.find_tile(&Tile::Start).unwrap(); + let end_pos = input.find_tile(&Tile::End).unwrap(); + + let mut queue = BinaryHeap::with_capacity(input.width()); + + queue.push(DState { + vert: (start_pos, Direction::East), + cost: 0, + prev: vec![], + }); + + let mut visited = HashMap::<(Position, Direction), usize>::with_capacity(input.width()); + + let mut all_good = HashSet::with_capacity(500); + + let mut found_min = None; + + while let Some(DState { + vert: (pos, dir), + cost, + prev, + }) = queue.pop() + { + if pos == end_pos { + //return Some(cost.to_string()); + if found_min.is_none_or(|s| s == cost) { + all_good.extend(prev.into_iter()); + all_good.insert(end_pos); + found_min = Some(cost); + continue; + } else { + break; + } + } + + if visited + .get(&(pos, dir)) + .is_some_and(|min_score| *min_score < cost) + { + continue; + } + + for (next_dir, score) in input + .relatives(pos, &[dir, dir.ninety_deg(true), dir.ninety_deg(false)]) + .filter_map(|(next_dir, _, t)| { + if *t != Tile::Wall { + Some((next_dir, if next_dir == dir { 1 } else { 1000 })) + } else { + None + } + }) + { + let mut next_prev = prev.clone(); + next_prev.push(pos); + let next_state = DState { + cost: cost + score, + vert: ( + if dir == next_dir { + pos.add(&dir.get_kernel()) + } else { + pos + }, + next_dir, + ), + prev: next_prev, + }; - day_stuff!(16, "", ""); + if next_state.cost <= *visited.get(&next_state.vert).unwrap_or(&usize::MAX) { + *visited.entry(next_state.vert).or_insert(usize::MAX) = next_state.cost; + queue.push(next_state); + } + } + } - fn part_1(_input: Self::Input) -> Option { - None + if all_good.contains(&end_pos) { + Some(all_good.len().to_string()) + } else { + panic!("No Solution!!!") + } } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + Grid::parse(input) } } diff --git a/years/2024/src/day_17.rs b/years/2024/src/day_17.rs index 7942c86..da1dd4d 100644 --- a/years/2024/src/day_17.rs +++ b/years/2024/src/day_17.rs @@ -1,17 +1,208 @@ - -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; pub struct Day17; +#[derive(Clone, Copy, Debug)] +pub struct Registers { + a: u128, + b: u128, + c: u128, +} + +impl Registers { + fn from_combo_op(&self, combo_op: &ComboOperand) -> u128 { + match combo_op { + ComboOperand::RegA => self.a, + ComboOperand::RegB => self.b, + ComboOperand::RegC => self.c, + _ => panic!(), + } + } +} + +#[derive(Clone, Copy, Debug)] +enum ComboOperand { + Literal(u128), + RegA, + RegB, + RegC, + Reserved, +} + +impl ComboOperand { + fn from_u128(v: u128) -> Self { + match v { + 4 => Self::RegA, + 5 => Self::RegB, + 6 => Self::RegC, + 7 => Self::Reserved, + o => Self::Literal(o), + } + } + + fn get_val(&self, regs: &Registers) -> u128 { + match self { + Self::Literal(v) => *v, + Self::Reserved => panic!(), + reg => regs.from_combo_op(reg), + } + } +} + +#[derive(Clone, Copy, Debug)] +enum Instruction { + Adv(ComboOperand), + Bxl(u128), + Bst(ComboOperand), + Jnz(u128), + Bxc(()), + Out(ComboOperand), + Bdv(ComboOperand), + Cdv(ComboOperand), +} + +impl Instruction { + fn from_slice(ins: &[u128]) -> Self { + let op = ins[0]; + let opr = ins[1]; + + match op { + 0 => Self::Adv(ComboOperand::from_u128(opr)), + 1 => Self::Bxl(opr), + 2 => Self::Bst(ComboOperand::from_u128(opr)), + 3 => Self::Jnz(opr), + 4 => Self::Bxc(()), + 5 => Self::Out(ComboOperand::from_u128(opr)), + 6 => Self::Bdv(ComboOperand::from_u128(opr)), + 7 => Self::Cdv(ComboOperand::from_u128(opr)), + _ => unreachable!(), + } + } + + fn execute(&self, ip: u128, regs: &mut Registers) -> (u128, Option) { + match self { + Self::Adv(op) => { + regs.a /= 2_u128.pow(op.get_val(regs) as u32); + } + Self::Bxl(v) => { + regs.b = regs.b ^ v; + } + Self::Bst(op) => { + regs.b = op.get_val(regs) % 8; + } + Self::Jnz(v) => { + if regs.a != 0 { + return (*v, None); + } + } + Self::Bxc(_) => { + regs.b = regs.b ^ regs.c; + } + Self::Out(op) => { + return (ip + 2, Some(op.get_val(regs) % 8)); + } + Self::Bdv(op) => { + regs.b = regs.a / 2_u128.pow(op.get_val(regs) as u32); + } + Self::Cdv(op) => { + regs.c = regs.a / 2_u128.pow(op.get_val(regs) as u32); + } + } + + (ip + 2, None) + } +} + +#[derive(Debug, Clone)] +pub struct Computer { + regs: Registers, + instructions: Vec, +} + +impl Computer { + fn parse(input: &str) -> Self { + let (raw_regs, raw_program) = input.trim().split_once("\n\n").unwrap(); + let mut regs = raw_regs + .lines() + .map(|l| l.split_once(": ").unwrap().1.parse::().unwrap()); + let regs = Registers { + a: regs.next().unwrap(), + b: regs.next().unwrap(), + c: regs.next().unwrap(), + }; + let instructions = raw_program + .split_once(": ") + .unwrap() + .1 + .split(',') + .map(|s| s.parse::().unwrap()) + .collect::>(); + + Self { regs, instructions } + } +} + impl Day for Day17 { + day_stuff!(17, "", "", Computer); + + fn part_1(mut input: Self::Input) -> Option { + let mut ip = 0; + let mut out = Vec::with_capacity(20); + while ip < input.instructions.len() - 1 { + let (next_ip, output) = Instruction::from_slice(&input.instructions[ip..=ip + 1]) + .execute(ip as u128, &mut input.regs); + if let Some(v) = output { + out.push(v.to_string()); + } + ip = next_ip as usize; + } + Some(out.join(",")) + } - day_stuff!(17, "", ""); + fn part_2(input: Self::Input) -> Option { + let mut possible_a = Vec::with_capacity(1000); + possible_a.push(0); + for ins in input.instructions.iter().rev().copied() { + possible_a = possible_a + .into_iter() + .flat_map(|a| { + let mut branch_possible = vec![]; + for possible_bits in 0_u128..=7 { + let new_a = (a << 3) + possible_bits; + let mut ip = 0; + let mut regs = Registers { + a: new_a, + b: 0, + c: 0, + }; + let val = loop { + if ip >= input.instructions.len() - 1 { + break None; + } + let (next_ip, output) = + Instruction::from_slice(&input.instructions[ip..=ip + 1]) + .execute(ip as u128, &mut regs); + if let Some(v) = output { + break Some(v); + } + ip = next_ip as usize; + }; - fn part_1(_input: Self::Input) -> Option { - None + if let Some(val) = val + && val == ins + { + branch_possible.push(new_a); + } + } + branch_possible + }) + .collect(); + } + let ans = possible_a.into_iter().min().map(|v| v.to_string()).unwrap(); + Some(ans) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + Computer::parse(input) } } diff --git a/years/2024/src/day_18.rs b/years/2024/src/day_18.rs index dc0d3a2..cf3f7af 100644 --- a/years/2024/src/day_18.rs +++ b/years/2024/src/day_18.rs @@ -1,17 +1,134 @@ +use std::{ + cmp::Ordering, + collections::{BinaryHeap, HashMap}, +}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{ipos, pos::Position, upos}; pub struct Day18; +#[derive(Clone, Eq, PartialEq)] +struct DState { + cost: usize, + pos: Position, + step_no: usize, +} + +impl Ord for DState { + fn cmp(&self, other: &Self) -> Ordering { + other.cost.cmp(&self.cost) + } +} + +impl PartialOrd for DState { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl Day for Day18 { + day_stuff!(18, "", "", Vec); - day_stuff!(18, "", ""); + fn part_1(input: Self::Input) -> Option { + let start_pos = Position::zero(); + let end_pos = ipos!(70, 70); - fn part_1(_input: Self::Input) -> Option { - None + let mut queue = BinaryHeap::new(); + + queue.push(DState { + cost: 0, + pos: start_pos, + step_no: 0, + }); + + let mut dist = HashMap::::new(); + + while let Some(DState { cost, pos, step_no }) = queue.pop() { + if pos == end_pos { + return Some(cost.to_string()); + } + + if dist.get(&pos).is_some_and(|min_score| *min_score < cost) { + continue; + } + + for (next_pos, _dir) in pos + .adjacents_checked((71, 71)) + .filter(|(p, _)| input.iter().take(1024).all(|op| op != p)) + { + let next_state = DState { + cost: cost + 1, + pos: next_pos, + step_no: step_no + 1, + }; + if next_state.cost < *dist.get(&next_state.pos).unwrap_or(&usize::MAX) { + *dist.entry(next_state.pos).or_insert(usize::MAX) = next_state.cost; + queue.push(next_state); + } + } + } + + panic!("No Path") } - fn part_2(_input: Self::Input) -> Option { + fn part_2(input: Self::Input) -> Option { + for i in 0..input.len() { + println!("Byte {} of {}", i + 1, input.len()); + let start_pos = Position::zero(); + let end_pos = ipos!(70, 70); + + let mut queue = BinaryHeap::new(); + + queue.push(DState { + cost: 0, + pos: start_pos, + step_no: 0, + }); + + let mut dist = HashMap::::new(); + let mut flag = false; + + while let Some(DState { cost, pos, step_no }) = queue.pop() { + if pos == end_pos { + flag = true; + break; + } + + if dist.get(&pos).is_some_and(|min_score| *min_score < cost) { + continue; + } + + for (next_pos, _dir) in pos + .adjacents_checked((71, 71)) + .filter(|(p, _)| input.iter().take(i + 1).all(|op| op != p)) + { + let next_state = DState { + cost: cost + 1, + pos: next_pos, + step_no: step_no + 1, + }; + if next_state.cost < *dist.get(&next_state.pos).unwrap_or(&usize::MAX) { + *dist.entry(next_state.pos).or_insert(usize::MAX) = next_state.cost; + queue.push(next_state); + } + } + } + if !flag { + return Some(input[i].to_string()); + } + } None } + + fn parse_input(input: &str) -> Self::Input { + input + .trim() + .lines() + .map(|l| { + let (x, y) = l.split_once(',').unwrap(); + upos!(x.parse::().unwrap(), y.parse::().unwrap()) + }) + .collect() + } } diff --git a/years/2024/src/day_19.rs b/years/2024/src/day_19.rs index d609fb9..58816b9 100644 --- a/years/2024/src/day_19.rs +++ b/years/2024/src/day_19.rs @@ -1,17 +1,67 @@ - -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use rayon::prelude::*; +use std::collections::{HashMap, HashSet}; pub struct Day19; impl Day for Day19 { + day_stuff!(19, "", "", (HashSet, Vec)); + + fn part_1(input: Self::Input) -> Option { + let (avail, desire) = input; + let ans = desire + .into_iter() + .filter(|pat| { + let pattern_ends = pat.len() + 1; + let mut seen = HashMap::with_capacity(pattern_ends); + seen.insert(0, 1); + for end in 1..pattern_ends { + for a in avail.iter().filter(|p| end >= p.len()) { + let avail_start = end - a.len(); + if &pat[avail_start..end] == a + && let Some(avail_start) = seen.get(&avail_start).copied() + { + *seen.entry(end).or_insert(0) += avail_start; + } + } + } + seen.get(&pat.len()).copied().is_some_and(|v| v != 0) + }) + .count(); + + Some(ans.to_string()) + } - day_stuff!(19, "", ""); + fn part_2(input: Self::Input) -> Option { + let (avail, desire) = input; + let ans = desire + .into_par_iter() + .map(|pat| { + let pattern_ends = pat.len() + 1; + let mut seen = HashMap::with_capacity(pattern_ends); + seen.insert(0, 1); + for end in 1..pattern_ends { + for a in avail.iter().filter(|p| end >= p.len()) { + let avail_start = end - a.len(); + if &pat[avail_start..end] == a + && let Some(avail_start) = seen.get(&avail_start).copied() + { + *seen.entry(end).or_insert(0) += avail_start; + } + } + } + seen.get(&pat.len()).copied().unwrap_or(0) + }) + .sum::(); - fn part_1(_input: Self::Input) -> Option { - None + Some(ans.to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + let (avail, desire) = input.trim().split_once("\n\n").unwrap(); + ( + avail.split(", ").map(|s| s.to_string()).collect(), + desire.split('\n').map(|s| s.to_string()).collect(), + ) } } diff --git a/years/2024/src/day_20.rs b/years/2024/src/day_20.rs index af4617f..992c7ca 100644 --- a/years/2024/src/day_20.rs +++ b/years/2024/src/day_20.rs @@ -1,17 +1,99 @@ +use std::collections::HashMap; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{dir::CARDINALS, pos::Position, tiles}; pub struct Day20; +tiles!(Tile, [ + '.' => Open, + '#' => Wall, + 'S' => Start, + 'E' => End, +]); + +type Grid = utils::grid::Grid; + impl Day for Day20 { + day_stuff!(20, "", "", Grid); + + fn part_1(input: Self::Input) -> Option { + let end_pos = input.find_tile(&Tile::End).unwrap(); + let start_pos = input.find_tile(&Tile::Start).unwrap(); + let mut costs = HashMap::with_capacity(100); + let mut curs = end_pos; + let mut cost = 0; + while curs != start_pos { + costs.insert(curs, cost); + let (_, next_pos, _) = input + .relatives(curs, &CARDINALS) + .filter(|(_, p, t)| **t != Tile::Wall && !costs.contains_key(p)) + .next() + .unwrap(); + curs = next_pos; + cost += 1; + } + costs.insert(start_pos, cost); + + let mut cheat_set = HashMap::<(Position, Position), usize>::with_capacity(costs.len()); + for (pos_a, cost_a) in costs.iter() { + for (pos_b, cost_b) in costs.iter() { + if cost_b < cost_a { + let diff = *cost_a - *cost_b; + let dist = pos_a.manhattan(&pos_b).abs() as usize; + if dist <= 2 { + cheat_set.insert((*pos_a, *pos_b), diff - dist); + } + } + } + } + + let ans = cheat_set.values().filter(|c| **c >= 100).count(); + + dbg!(cheat_set.len()); + + Some(ans.to_string()) + } + + fn part_2(input: Self::Input) -> Option { + let end_pos = input.find_tile(&Tile::End).unwrap(); + let start_pos = input.find_tile(&Tile::Start).unwrap(); + let mut costs = HashMap::with_capacity(100); + let mut curs = end_pos; + let mut cost = 0; + while curs != start_pos { + costs.insert(curs, cost); + let (_, next_pos, _) = input + .relatives(curs, &CARDINALS) + .filter(|(_, p, t)| **t != Tile::Wall && !costs.contains_key(p)) + .next() + .unwrap(); + curs = next_pos; + cost += 1; + } + costs.insert(start_pos, cost); + + let mut cheat_set = HashMap::<(Position, Position), usize>::with_capacity(costs.len()); + for (pos_a, cost_a) in costs.iter() { + for (pos_b, cost_b) in costs.iter() { + if cost_b < cost_a { + let diff = *cost_a - *cost_b; + let dist = pos_a.manhattan(&pos_b).abs() as usize; + if dist <= 20 { + cheat_set.insert((*pos_a, *pos_b), diff - dist); + } + } + } + } + + let ans = cheat_set.values().filter(|c| **c >= 100).count(); - day_stuff!(20, "", ""); + dbg!(cheat_set.len()); - fn part_1(_input: Self::Input) -> Option { - None + Some(ans.to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + Grid::parse(input) } } diff --git a/years/2024/src/day_21.rs b/years/2024/src/day_21.rs index afaf556..4d6acd8 100644 --- a/years/2024/src/day_21.rs +++ b/years/2024/src/day_21.rs @@ -1,17 +1,162 @@ +use std::collections::{HashMap, VecDeque}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{ + dir::{Direction, CARDINALS}, + pos::Position, +}; pub struct Day21; +const NUMPAD: &str = "789\n456\n123\n#0A"; +const DIRPAD: &str = "#^A\n"; + +type Grid = utils::grid::Grid; + +fn pad_grids() -> (Grid, Grid) { + (Grid::parse(NUMPAD), Grid::parse(DIRPAD)) +} + +fn dir_to_char(dir: Direction) -> char { + match dir { + Direction::East => '>', + Direction::West => '<', + Direction::North => '^', + Direction::South => 'v', + } +} + +type BestMap = HashMap<(char, char), Vec>>; + +fn find_best_paths(g: &Grid) -> BestMap { + let mut costs = BestMap::with_capacity(18); + + for (pos1, tile1) in g.iter().filter(|(_, t)| **t != '#') { + costs.insert((*tile1, *tile1), vec![vec![]]); + for (pos2, tile2) in g.iter().filter(|(_, t)| **t != '#' && **t != *tile1) { + let mut queue = VecDeque::<(Position, Vec)>::with_capacity(18); + queue.push_back((pos1, vec![])); + while let Some((curr_pos, path)) = queue.pop_front() { + if costs + .get(&(*tile1, *tile2)) + .is_some_and(|c| c[0].len() < path.len()) + { + continue; + } + + if curr_pos == pos2 { + (*costs.entry((*tile1, *tile2)).or_insert(vec![])).push(path); + continue; + } + + for (dir, new_pos, _) in g + .relatives(curr_pos, &CARDINALS) + .filter(|(_, _, t)| **t != '#' && **t != *tile1) + { + let mut new_path = path.clone(); + new_path.push(dir_to_char(dir)); + queue.push_back((new_pos, new_path)); + } + } + } + } + + costs + .values_mut() + .for_each(|v| v.iter_mut().for_each(|p| p.push('A'))); + + costs +} + +fn recur_find( + seq: &[char], + level: usize, + top: bool, + robos: &mut Vec, + num_best: &BestMap, + dir_best: &BestMap, + dp: &mut HashMap<(Vec, usize, char), usize>, +) -> usize { + let key = (seq.to_vec(), level, robos[level]); + if let Some(&res) = dp.get(&key) { + return res; + } + + let mut final_val = 0; + + for &c in seq { + let all_paths = (if top { num_best } else { dir_best }) + .get(&(robos[level], c)) + .unwrap(); + final_val += if level == 0 { + all_paths.iter().map(Vec::len).min() + } else { + all_paths + .iter() + .map(|path| recur_find(path, level - 1, false, robos, num_best, dir_best, dp)) + .min() + } + .unwrap(); + robos[level] = c; + } + + dp.insert(key, final_val); + + final_val +} + impl Day for Day21 { + day_stuff!(21, "", "", Vec<(usize, Vec)>); + + fn part_1(input: Self::Input) -> Option { + let (num_grid, dir_grid) = pad_grids(); + let (num_best, dir_best) = (find_best_paths(&num_grid), find_best_paths(&dir_grid)); + let mut dp = HashMap::new(); + let ans = input + .into_iter() + .map(|(num, code)| { + let mut robos = vec!['A'; 3]; + let best_path = + recur_find(&code, 2, true, &mut robos, &num_best, &dir_best, &mut dp); + + best_path * num + }) + .sum::(); + + Some(ans.to_string()) + } + + fn part_2(input: Self::Input) -> Option { + let (num_grid, dir_grid) = pad_grids(); + let (num_best, dir_best) = (find_best_paths(&num_grid), find_best_paths(&dir_grid)); + let mut dp = HashMap::new(); + let ans = input + .into_iter() + .map(|(num, code)| { + let mut robos = vec!['A'; 26]; + let best_path = + recur_find(&code, 25, true, &mut robos, &num_best, &dir_best, &mut dp); - day_stuff!(21, "", ""); + best_path * num + }) + .sum::(); - fn part_1(_input: Self::Input) -> Option { - None + Some(ans.to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + input + .trim() + .lines() + .map(|l| { + ( + l.trim_start_matches('0') + .trim_end_matches('A') + .parse::() + .unwrap(), + l.chars().collect(), + ) + }) + .collect() } } diff --git a/years/2024/src/day_22.rs b/years/2024/src/day_22.rs index 6d4b4a2..042fc5d 100644 --- a/years/2024/src/day_22.rs +++ b/years/2024/src/day_22.rs @@ -1,17 +1,88 @@ +use std::collections::{HashMap, HashSet}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; pub struct Day22; +fn next_secret(mut num: usize) -> usize { + num = ((num * 64) ^ num) % 16777216; + num = ((num / 32) ^ num) % 16777216; + num = ((num * 2048) ^ num) % 16777216; + num +} + +fn secret_n_times(init: usize, times: usize) -> usize { + let mut num = init; + for _ in 0..times { + num = next_secret(num); + } + num +} + +fn get_all_four_unique_changes(init: usize, times: usize) -> HashMap<[isize; 4], usize> { + let mut last = 0; + let mut changes = Vec::with_capacity(times + 1); + let mut curr = init; + for _ in 0..times { + curr = next_secret(curr); + let val = curr % 10; + changes.push(((val as isize) - (last as isize), val)); + last = val; + } + + changes + .windows(4) + .fold(HashMap::with_capacity(times / 4), |mut acc, w| { + let changes = [w[0].0, w[1].0, w[2].0, w[3].0]; + let final_val = w[3].1; + if !acc.contains_key(&changes) { + acc.insert(changes, final_val); + } + acc + }) +} + impl Day for Day22 { + day_stuff!(22, "", "", Vec); + + fn part_1(input: Self::Input) -> Option { + let ans = input + .into_iter() + .map(|init| secret_n_times(init, 2000)) + .sum::(); + + Some(ans.to_string()) + } + + fn part_2(input: Self::Input) -> Option { + let change_to_val = input + .into_iter() + .map(|init| get_all_four_unique_changes(init, 2000)) + .collect::>(); + let all_changes = change_to_val + .iter() + .flat_map(|h| h.keys().copied()) + .collect::>(); - day_stuff!(22, "", ""); + let ans = all_changes + .into_iter() + .map(|c| { + change_to_val + .iter() + .map(|h| h.get(&c).unwrap_or(&0)) + .sum::() + }) + .max() + .unwrap(); - fn part_1(_input: Self::Input) -> Option { - None + Some(ans.to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + input + .trim() + .lines() + .map(|l| l.parse::().unwrap()) + .collect() } } -- 2.51.2