Advent of Code solutions

More days, I haven't committed in a while

bwc9876.dev 8af887ad eab508d5

verified
+1093 -66
+4
.gitignore
··· 4 4 5 5 # For 2024 Day 14 - I am in hell 6 6 trees*.txt 7 + mem.mem 8 + 9 + # Nice one rustc 10 + rustc-ice* 7 11
+52
Cargo.lock
··· 54 54 ] 55 55 56 56 [[package]] 57 + name = "crossbeam-deque" 58 + version = "0.8.6" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 61 + dependencies = [ 62 + "crossbeam-epoch", 63 + "crossbeam-utils", 64 + ] 65 + 66 + [[package]] 67 + name = "crossbeam-epoch" 68 + version = "0.9.18" 69 + source = "registry+https://github.com/rust-lang/crates.io-index" 70 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 71 + dependencies = [ 72 + "crossbeam-utils", 73 + ] 74 + 75 + [[package]] 76 + name = "crossbeam-utils" 77 + version = "0.8.21" 78 + source = "registry+https://github.com/rust-lang/crates.io-index" 79 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 80 + 81 + [[package]] 82 + name = "either" 83 + version = "1.13.0" 84 + source = "registry+https://github.com/rust-lang/crates.io-index" 85 + checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 86 + 87 + [[package]] 57 88 name = "encode_unicode" 58 89 version = "0.3.6" 59 90 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 147 178 checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 148 179 dependencies = [ 149 180 "proc-macro2", 181 + ] 182 + 183 + [[package]] 184 + name = "rayon" 185 + version = "1.10.0" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 188 + dependencies = [ 189 + "either", 190 + "rayon-core", 191 + ] 192 + 193 + [[package]] 194 + name = "rayon-core" 195 + version = "1.12.1" 196 + source = "registry+https://github.com/rust-lang/crates.io-index" 197 + checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 198 + dependencies = [ 199 + "crossbeam-deque", 200 + "crossbeam-utils", 150 201 ] 151 202 152 203 [[package]] ··· 355 406 dependencies = [ 356 407 "advent_core", 357 408 "macros", 409 + "rayon", 358 410 "regex", 359 411 "utils", 360 412 ]
+1 -1
utils/src/num.rs
··· 29 29 /// Split a given number at a specific digit, this digit will be included in the right-hand side 30 30 /// and excluded in the left. 31 31 /// 32 - /// If the split is invalid, zero may be returnd on either side of the result. 32 + /// If the split is invalid, zero may be returned on either side of the result. 33 33 /// 34 34 /// # Examples 35 35 ///
+1
years/2024/Cargo.toml
··· 7 7 [dependencies] 8 8 advent_core = { path = "../../advent_core" } 9 9 macros = { path = "../../macros" } 10 + rayon = "1.10.0" 10 11 regex = "1.11.1" 11 12 utils = { path = "../../utils" }
+10 -16
years/2024/src/day_14.rs
··· 2 2 3 3 use advent_core::{day_stuff, ex_for_day, Day}; 4 4 use regex::Regex; 5 - use utils::{ipos, pos::Position}; 5 + use utils::{pos::Position, upos}; 6 6 7 7 pub struct Day14; 8 8 9 - fn robot_go(pos: Position, vel: Position, times: isize, bounds: Position) -> Position { 10 - let new_pos = pos.add(&vel.multiply_comp(times)); 11 - let x_r = new_pos.x % bounds.x; 12 - let y_r = new_pos.y % bounds.y; 13 - ipos!( 14 - if x_r < 0 { x_r + bounds.x } else { x_r }, 15 - if y_r < 0 { y_r + bounds.y } else { y_r } 16 - ) 9 + fn robot_go(pos: Position, vel: Position, times: isize, bounds: (usize, usize)) -> Position { 10 + pos.add(&vel.multiply_comp(times)).bind(bounds).into() 17 11 } 18 12 19 13 impl Day for Day14 { 20 14 day_stuff!(14, "", "", Vec<(Position, Position)>); 21 15 22 16 fn part_1(input: Self::Input) -> Option<String> { 23 - let bounds = Position::new(101, 103); 17 + let bounds = (101, 103); 24 18 let times = 100; 25 19 let (ur, ul, ll, lr) = input 26 20 .into_iter() 27 21 .map(move |(pos, vel)| robot_go(pos, vel, times, bounds)) 28 22 .fold((0, 0, 0, 0), move |mut acc, robo| { 29 - let is_upper = match robo.y.cmp(&(bounds.y / 2)) { 23 + let is_upper = match robo.y.cmp(&(bounds.1 as isize / 2)) { 30 24 Ordering::Equal => None, 31 25 Ordering::Greater => Some(false), 32 26 Ordering::Less => Some(true), 33 27 }; 34 - let is_left = match robo.x.cmp(&(bounds.x / 2)) { 28 + let is_left = match robo.x.cmp(&(bounds.0 as isize / 2)) { 35 29 Ordering::Equal => None, 36 30 Ordering::Greater => Some(false), 37 31 Ordering::Less => Some(true), ··· 53 47 } 54 48 55 49 fn part_2(input: Self::Input) -> Option<String> { 56 - let bounds = Position::new(101, 103); 50 + let bounds = (101, 103); 57 51 58 52 let re = Regex::new(include_str!("da_tree.txt")).unwrap(); 59 53 ··· 63 57 .map(move |r| robot_go(r.0, r.1, i as isize, bounds)) 64 58 .collect::<HashSet<_>>(); 65 59 66 - let hay = (0..bounds.y) 60 + let hay = (0..bounds.1) 67 61 .flat_map(|y| { 68 62 let bots = &bots; 69 - (0..bounds.x) 63 + (0..bounds.0) 70 64 .map(move |x| { 71 - let pos = Position::new(x, y); 65 + let pos = upos!(x, y); 72 66 if bots.contains(&pos) { 73 67 'X' 74 68 } else {
+148 -6
years/2024/src/day_15.rs
··· 1 + use std::collections::{HashMap, HashSet}; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 4 + use utils::{ 5 + dir::{Direction, Movement}, 6 + grid::Grid, 7 + pos::Position, 8 + tiles, upos, 9 + }; 3 10 4 11 pub struct Day15; 5 12 13 + tiles!(Tile, [ 14 + '.' => Empty, 15 + '@' => Robot, 16 + '#' => Wall, 17 + 'O' => Box, 18 + '[' => BoxLeft, 19 + ']' => BoxRight, 20 + ]); 21 + 22 + fn parse_instruction(c: char) -> Direction { 23 + match c { 24 + '^' => Direction::North, 25 + '<' => Direction::West, 26 + '>' => Direction::East, 27 + 'v' => Direction::South, 28 + _ => unreachable!("For {c:?}"), 29 + } 30 + } 31 + 32 + type PosMap = HashMap<Position, Tile>; 33 + 34 + fn movement(robo: Position, dir: Direction, map: &mut PosMap) -> Option<Position> { 35 + let mut next_pos = robo.add(&dir.get_kernel()); 36 + let mut to_update = Vec::with_capacity(20); 37 + loop { 38 + let next_tile = map.get(&next_pos).unwrap(); 39 + if *next_tile == Tile::Wall { 40 + return None; 41 + } else if *next_tile == Tile::Box { 42 + to_update.push(next_pos); 43 + next_pos = next_pos.add(&dir.get_kernel()); 44 + } else { 45 + // Is Empty 46 + to_update.push(next_pos); 47 + break; 48 + } 49 + } 50 + assert!(!to_update.is_empty()); 51 + let first = to_update.first().unwrap(); 52 + *map.get_mut(first).unwrap() = Tile::Robot; 53 + *map.get_mut(&robo).unwrap() = Tile::Empty; 54 + if to_update.len() >= 2 { 55 + *map.get_mut(to_update.last().unwrap()).unwrap() = Tile::Box; 56 + } 57 + Some(*first) 58 + } 59 + 60 + fn movement_pt_2(robo: Position, dir: Direction, map: &mut PosMap) -> Option<Position> { 61 + let kern = dir.get_kernel(); 62 + let mut to_check = HashSet::from_iter([robo.add(&kern)]); 63 + let mut to_update = Vec::with_capacity(40); 64 + to_update.push((robo.add(&kern), Tile::Robot)); 65 + while !to_check.is_empty() { 66 + let mut new_check = HashSet::new(); 67 + for check in to_check.into_iter() { 68 + let new_tile = *map.get(&check).unwrap(); 69 + if new_tile == Tile::Wall { 70 + return None; 71 + } else if new_tile == Tile::BoxLeft && !dir.is_horizontal() { 72 + let my_new_pos = check.add(&kern); 73 + let r_pos = check.add(&upos!(1, 0)); 74 + let r_new_pos = r_pos.add(&kern); 75 + to_update.push((my_new_pos, Tile::BoxLeft)); 76 + to_update.push((r_new_pos, Tile::BoxRight)); 77 + new_check.insert(my_new_pos); 78 + new_check.insert(r_new_pos); 79 + } else if new_tile == Tile::BoxRight && !dir.is_horizontal() { 80 + let my_new_pos = check.add(&kern); 81 + let l_pos = check.sub(&upos!(1, 0)); 82 + let l_new_pos = l_pos.add(&kern); 83 + to_update.push((my_new_pos, Tile::BoxRight)); 84 + to_update.push((l_new_pos, Tile::BoxLeft)); 85 + new_check.insert(my_new_pos); 86 + new_check.insert(l_new_pos); 87 + } else if new_tile != Tile::Empty { 88 + to_update.push((check.add(&kern), new_tile)); 89 + new_check.insert(check.add(&kern)); 90 + } 91 + } 92 + to_check = new_check; 93 + } 94 + for (pos, tile) in to_update.into_iter().rev() { 95 + *map.get_mut(&pos).unwrap() = tile; 96 + *map.get_mut(&pos.sub(&kern)).unwrap() = Tile::Empty; 97 + } 98 + Some(robo.add(&kern)) 99 + } 100 + 101 + fn gps(pos_map: &PosMap) -> usize { 102 + pos_map 103 + .iter() 104 + .filter_map(|(pos, tile)| { 105 + if matches!(*tile, Tile::Box | Tile::BoxLeft) { 106 + Some(100 * pos.y as usize + pos.x as usize) 107 + } else { 108 + None 109 + } 110 + }) 111 + .sum() 112 + } 113 + 6 114 impl Day for Day15 { 115 + day_stuff!(15, "", "", (Position, PosMap, Vec<Direction>)); 7 116 8 - day_stuff!(15, "", ""); 117 + fn part_1(input: Self::Input) -> Option<String> { 118 + let (mut robo, mut pos_map, ins) = input; 119 + for i in ins { 120 + if let Some(new_pos) = movement(robo, i, dbg!(&mut pos_map)) { 121 + robo = new_pos; 122 + } 123 + } 124 + Some(gps(&pos_map).to_string()) 125 + } 9 126 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 127 + fn part_2(input: Self::Input) -> Option<String> { 128 + let (mut robo, mut pos_map, ins) = input; 129 + for i in ins { 130 + if let Some(new_pos) = movement_pt_2(robo, i, &mut pos_map) { 131 + println!("Move success"); 132 + robo = new_pos; 133 + } 134 + } 135 + Some(gps(&pos_map).to_string()) 12 136 } 13 137 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 138 + fn parse_input(input: &str) -> Self::Input { 139 + // TODO: Temp for pt 2 140 + let replace = input.replace('#', "##"); 141 + let replace = replace.replace('.', ".."); 142 + let replace = replace.replace('O', "[]"); 143 + let replace = replace.replace('@', "@."); 144 + println!("{}", &replace); 145 + let (map, dirs) = &replace.trim().split_once("\n\n").unwrap(); 146 + let dirs = dirs 147 + .split('\n') 148 + .flat_map(|l| l.chars().map(parse_instruction)) 149 + .collect(); 150 + 151 + let grid = Grid::<Tile>::parse(map); 152 + 153 + let robo = grid.find_tile(&Tile::Robot).unwrap(); 154 + 155 + let pos_map = grid.iter().map(|(pos, tile)| (pos, *tile)).collect(); 156 + 157 + (robo, pos_map, dirs) 16 158 } 17 159 }
+184 -6
years/2024/src/day_16.rs
··· 1 + use std::{ 2 + cmp::Ordering, 3 + collections::{BinaryHeap, HashMap, HashSet}, 4 + }; 1 5 2 - use advent_core::{Day, day_stuff, ex_for_day}; 6 + use advent_core::{day_stuff, ex_for_day, Day}; 7 + use utils::{ 8 + dir::{Direction, Movement}, 9 + pos::Position, 10 + tiles, 11 + }; 3 12 4 13 pub struct Day16; 5 14 15 + tiles!(Tile, [ 16 + '.' => Open, 17 + '#' => Wall, 18 + 'S' => Start, 19 + 'E' => End, 20 + ]); 21 + 22 + type Grid = utils::grid::Grid<Tile>; 23 + 24 + #[derive(Clone, Eq, PartialEq)] 25 + struct DState { 26 + cost: usize, 27 + vert: (Position, Direction), 28 + prev: Vec<Position>, 29 + } 30 + 31 + impl Ord for DState { 32 + fn cmp(&self, other: &Self) -> Ordering { 33 + other.cost.cmp(&self.cost) 34 + } 35 + } 36 + 37 + impl PartialOrd for DState { 38 + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 39 + Some(self.cmp(other)) 40 + } 41 + } 42 + 6 43 impl Day for Day16 { 44 + day_stuff!(16, "", "", Grid); 7 45 8 - day_stuff!(16, "", ""); 46 + fn part_1(input: Self::Input) -> Option<String> { 47 + let start_pos = input.find_tile(&Tile::Start).unwrap(); 48 + let end_pos = input.find_tile(&Tile::End).unwrap(); 49 + 50 + let mut queue = BinaryHeap::new(); 51 + 52 + queue.push(DState { 53 + vert: (start_pos, Direction::East), 54 + cost: 0, 55 + prev: vec![], 56 + }); 57 + 58 + let mut visited = HashMap::<(Position, Direction), usize>::new(); 9 59 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 60 + while let Some(DState { 61 + vert: (pos, dir), 62 + cost, 63 + prev: _, 64 + }) = queue.pop() 65 + { 66 + if pos == end_pos { 67 + return Some(cost.to_string()); 68 + } 69 + 70 + if visited 71 + .get(&(pos, dir)) 72 + .is_some_and(|min_score| *min_score < cost) 73 + { 74 + continue; 75 + } 76 + 77 + for (next_dir, score) in input 78 + .relatives(pos, &[dir, dir.ninety_deg(true), dir.ninety_deg(false)]) 79 + .filter_map(|(next_dir, _, t)| { 80 + if *t != Tile::Wall { 81 + Some((next_dir, if next_dir == dir { 1 } else { 1000 })) 82 + } else { 83 + None 84 + } 85 + }) 86 + { 87 + let next_state = DState { 88 + cost: cost + score, 89 + vert: ( 90 + if dir == next_dir { 91 + pos.add(&dir.get_kernel()) 92 + } else { 93 + pos 94 + }, 95 + next_dir, 96 + ), 97 + prev: vec![], 98 + }; 99 + 100 + if next_state.cost < *visited.get(&next_state.vert).unwrap_or(&usize::MAX) { 101 + *visited.entry(next_state.vert).or_insert(usize::MAX) = next_state.cost; 102 + queue.push(next_state); 103 + } 104 + } 105 + } 106 + 107 + panic!("No Solution!!!") 12 108 } 13 109 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 110 + fn part_2(input: Self::Input) -> Option<String> { 111 + let start_pos = input.find_tile(&Tile::Start).unwrap(); 112 + let end_pos = input.find_tile(&Tile::End).unwrap(); 113 + 114 + let mut queue = BinaryHeap::with_capacity(input.width()); 115 + 116 + queue.push(DState { 117 + vert: (start_pos, Direction::East), 118 + cost: 0, 119 + prev: vec![], 120 + }); 121 + 122 + let mut visited = HashMap::<(Position, Direction), usize>::with_capacity(input.width()); 123 + 124 + let mut all_good = HashSet::with_capacity(500); 125 + 126 + let mut found_min = None; 127 + 128 + while let Some(DState { 129 + vert: (pos, dir), 130 + cost, 131 + prev, 132 + }) = queue.pop() 133 + { 134 + if pos == end_pos { 135 + //return Some(cost.to_string()); 136 + if found_min.is_none_or(|s| s == cost) { 137 + all_good.extend(prev.into_iter()); 138 + all_good.insert(end_pos); 139 + found_min = Some(cost); 140 + continue; 141 + } else { 142 + break; 143 + } 144 + } 145 + 146 + if visited 147 + .get(&(pos, dir)) 148 + .is_some_and(|min_score| *min_score < cost) 149 + { 150 + continue; 151 + } 152 + 153 + for (next_dir, score) in input 154 + .relatives(pos, &[dir, dir.ninety_deg(true), dir.ninety_deg(false)]) 155 + .filter_map(|(next_dir, _, t)| { 156 + if *t != Tile::Wall { 157 + Some((next_dir, if next_dir == dir { 1 } else { 1000 })) 158 + } else { 159 + None 160 + } 161 + }) 162 + { 163 + let mut next_prev = prev.clone(); 164 + next_prev.push(pos); 165 + let next_state = DState { 166 + cost: cost + score, 167 + vert: ( 168 + if dir == next_dir { 169 + pos.add(&dir.get_kernel()) 170 + } else { 171 + pos 172 + }, 173 + next_dir, 174 + ), 175 + prev: next_prev, 176 + }; 177 + 178 + if next_state.cost <= *visited.get(&next_state.vert).unwrap_or(&usize::MAX) { 179 + *visited.entry(next_state.vert).or_insert(usize::MAX) = next_state.cost; 180 + queue.push(next_state); 181 + } 182 + } 183 + } 184 + 185 + if all_good.contains(&end_pos) { 186 + Some(all_good.len().to_string()) 187 + } else { 188 + panic!("No Solution!!!") 189 + } 190 + } 191 + 192 + fn parse_input(input: &str) -> Self::Input { 193 + Grid::parse(input) 16 194 } 17 195 }
+198 -7
years/2024/src/day_17.rs
··· 1 + use advent_core::{day_stuff, ex_for_day, Day}; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + pub struct Day17; 4 + 5 + #[derive(Clone, Copy, Debug)] 6 + pub struct Registers { 7 + a: u128, 8 + b: u128, 9 + c: u128, 10 + } 11 + 12 + impl Registers { 13 + fn from_combo_op(&self, combo_op: &ComboOperand) -> u128 { 14 + match combo_op { 15 + ComboOperand::RegA => self.a, 16 + ComboOperand::RegB => self.b, 17 + ComboOperand::RegC => self.c, 18 + _ => panic!(), 19 + } 20 + } 21 + } 22 + 23 + #[derive(Clone, Copy, Debug)] 24 + enum ComboOperand { 25 + Literal(u128), 26 + RegA, 27 + RegB, 28 + RegC, 29 + Reserved, 30 + } 31 + 32 + impl ComboOperand { 33 + fn from_u128(v: u128) -> Self { 34 + match v { 35 + 4 => Self::RegA, 36 + 5 => Self::RegB, 37 + 6 => Self::RegC, 38 + 7 => Self::Reserved, 39 + o => Self::Literal(o), 40 + } 41 + } 42 + 43 + fn get_val(&self, regs: &Registers) -> u128 { 44 + match self { 45 + Self::Literal(v) => *v, 46 + Self::Reserved => panic!(), 47 + reg => regs.from_combo_op(reg), 48 + } 49 + } 50 + } 51 + 52 + #[derive(Clone, Copy, Debug)] 53 + enum Instruction { 54 + Adv(ComboOperand), 55 + Bxl(u128), 56 + Bst(ComboOperand), 57 + Jnz(u128), 58 + Bxc(()), 59 + Out(ComboOperand), 60 + Bdv(ComboOperand), 61 + Cdv(ComboOperand), 62 + } 63 + 64 + impl Instruction { 65 + fn from_slice(ins: &[u128]) -> Self { 66 + let op = ins[0]; 67 + let opr = ins[1]; 68 + 69 + match op { 70 + 0 => Self::Adv(ComboOperand::from_u128(opr)), 71 + 1 => Self::Bxl(opr), 72 + 2 => Self::Bst(ComboOperand::from_u128(opr)), 73 + 3 => Self::Jnz(opr), 74 + 4 => Self::Bxc(()), 75 + 5 => Self::Out(ComboOperand::from_u128(opr)), 76 + 6 => Self::Bdv(ComboOperand::from_u128(opr)), 77 + 7 => Self::Cdv(ComboOperand::from_u128(opr)), 78 + _ => unreachable!(), 79 + } 80 + } 81 + 82 + fn execute(&self, ip: u128, regs: &mut Registers) -> (u128, Option<u128>) { 83 + match self { 84 + Self::Adv(op) => { 85 + regs.a /= 2_u128.pow(op.get_val(regs) as u32); 86 + } 87 + Self::Bxl(v) => { 88 + regs.b = regs.b ^ v; 89 + } 90 + Self::Bst(op) => { 91 + regs.b = op.get_val(regs) % 8; 92 + } 93 + Self::Jnz(v) => { 94 + if regs.a != 0 { 95 + return (*v, None); 96 + } 97 + } 98 + Self::Bxc(_) => { 99 + regs.b = regs.b ^ regs.c; 100 + } 101 + Self::Out(op) => { 102 + return (ip + 2, Some(op.get_val(regs) % 8)); 103 + } 104 + Self::Bdv(op) => { 105 + regs.b = regs.a / 2_u128.pow(op.get_val(regs) as u32); 106 + } 107 + Self::Cdv(op) => { 108 + regs.c = regs.a / 2_u128.pow(op.get_val(regs) as u32); 109 + } 110 + } 111 + 112 + (ip + 2, None) 113 + } 114 + } 115 + 116 + #[derive(Debug, Clone)] 117 + pub struct Computer { 118 + regs: Registers, 119 + instructions: Vec<u128>, 120 + } 121 + 122 + impl Computer { 123 + fn parse(input: &str) -> Self { 124 + let (raw_regs, raw_program) = input.trim().split_once("\n\n").unwrap(); 125 + let mut regs = raw_regs 126 + .lines() 127 + .map(|l| l.split_once(": ").unwrap().1.parse::<u128>().unwrap()); 128 + let regs = Registers { 129 + a: regs.next().unwrap(), 130 + b: regs.next().unwrap(), 131 + c: regs.next().unwrap(), 132 + }; 133 + let instructions = raw_program 134 + .split_once(": ") 135 + .unwrap() 136 + .1 137 + .split(',') 138 + .map(|s| s.parse::<u128>().unwrap()) 139 + .collect::<Vec<_>>(); 3 140 4 - pub struct Day17; 141 + Self { regs, instructions } 142 + } 143 + } 5 144 6 145 impl Day for Day17 { 146 + day_stuff!(17, "", "", Computer); 7 147 8 - day_stuff!(17, "", ""); 148 + fn part_1(mut input: Self::Input) -> Option<String> { 149 + let mut ip = 0; 150 + let mut out = Vec::with_capacity(20); 151 + while ip < input.instructions.len() - 1 { 152 + let (next_ip, output) = Instruction::from_slice(&input.instructions[ip..=ip + 1]) 153 + .execute(ip as u128, &mut input.regs); 154 + if let Some(v) = output { 155 + out.push(v.to_string()); 156 + } 157 + ip = next_ip as usize; 158 + } 159 + Some(out.join(",")) 160 + } 9 161 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 162 + fn part_2(input: Self::Input) -> Option<String> { 163 + let mut possible_a = Vec::with_capacity(1000); 164 + possible_a.push(0); 165 + for ins in input.instructions.iter().rev().copied() { 166 + possible_a = possible_a 167 + .into_iter() 168 + .flat_map(|a| { 169 + let mut branch_possible = vec![]; 170 + for possible_bits in 0_u128..=7 { 171 + let new_a = (a << 3) + possible_bits; 172 + let mut ip = 0; 173 + let mut regs = Registers { 174 + a: new_a, 175 + b: 0, 176 + c: 0, 177 + }; 178 + let val = loop { 179 + if ip >= input.instructions.len() - 1 { 180 + break None; 181 + } 182 + let (next_ip, output) = 183 + Instruction::from_slice(&input.instructions[ip..=ip + 1]) 184 + .execute(ip as u128, &mut regs); 185 + if let Some(v) = output { 186 + break Some(v); 187 + } 188 + ip = next_ip as usize; 189 + }; 190 + 191 + if let Some(val) = val 192 + && val == ins 193 + { 194 + branch_possible.push(new_a); 195 + } 196 + } 197 + branch_possible 198 + }) 199 + .collect(); 200 + } 201 + let ans = possible_a.into_iter().min().map(|v| v.to_string()).unwrap(); 202 + Some(ans) 12 203 } 13 204 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 205 + fn parse_input(input: &str) -> Self::Input { 206 + Computer::parse(input) 16 207 } 17 208 }
+122 -5
years/2024/src/day_18.rs
··· 1 + use std::{ 2 + cmp::Ordering, 3 + collections::{BinaryHeap, HashMap}, 4 + }; 1 5 2 - use advent_core::{Day, day_stuff, ex_for_day}; 6 + use advent_core::{day_stuff, ex_for_day, Day}; 7 + use utils::{ipos, pos::Position, upos}; 3 8 4 9 pub struct Day18; 5 10 11 + #[derive(Clone, Eq, PartialEq)] 12 + struct DState { 13 + cost: usize, 14 + pos: Position, 15 + step_no: usize, 16 + } 17 + 18 + impl Ord for DState { 19 + fn cmp(&self, other: &Self) -> Ordering { 20 + other.cost.cmp(&self.cost) 21 + } 22 + } 23 + 24 + impl PartialOrd for DState { 25 + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 26 + Some(self.cmp(other)) 27 + } 28 + } 29 + 6 30 impl Day for Day18 { 31 + day_stuff!(18, "", "", Vec<Position>); 7 32 8 - day_stuff!(18, "", ""); 33 + fn part_1(input: Self::Input) -> Option<String> { 34 + let start_pos = Position::zero(); 35 + let end_pos = ipos!(70, 70); 36 + 37 + let mut queue = BinaryHeap::new(); 38 + 39 + queue.push(DState { 40 + cost: 0, 41 + pos: start_pos, 42 + step_no: 0, 43 + }); 44 + 45 + let mut dist = HashMap::<Position, usize>::new(); 46 + 47 + while let Some(DState { cost, pos, step_no }) = queue.pop() { 48 + if pos == end_pos { 49 + return Some(cost.to_string()); 50 + } 51 + 52 + if dist.get(&pos).is_some_and(|min_score| *min_score < cost) { 53 + continue; 54 + } 55 + 56 + for (next_pos, _dir) in pos 57 + .adjacents_checked((71, 71)) 58 + .filter(|(p, _)| input.iter().take(1024).all(|op| op != p)) 59 + { 60 + let next_state = DState { 61 + cost: cost + 1, 62 + pos: next_pos, 63 + step_no: step_no + 1, 64 + }; 65 + if next_state.cost < *dist.get(&next_state.pos).unwrap_or(&usize::MAX) { 66 + *dist.entry(next_state.pos).or_insert(usize::MAX) = next_state.cost; 67 + queue.push(next_state); 68 + } 69 + } 70 + } 71 + 72 + panic!("No Path") 73 + } 74 + 75 + fn part_2(input: Self::Input) -> Option<String> { 76 + for i in 0..input.len() { 77 + println!("Byte {} of {}", i + 1, input.len()); 78 + let start_pos = Position::zero(); 79 + let end_pos = ipos!(70, 70); 80 + 81 + let mut queue = BinaryHeap::new(); 82 + 83 + queue.push(DState { 84 + cost: 0, 85 + pos: start_pos, 86 + step_no: 0, 87 + }); 88 + 89 + let mut dist = HashMap::<Position, usize>::new(); 90 + let mut flag = false; 91 + 92 + while let Some(DState { cost, pos, step_no }) = queue.pop() { 93 + if pos == end_pos { 94 + flag = true; 95 + break; 96 + } 97 + 98 + if dist.get(&pos).is_some_and(|min_score| *min_score < cost) { 99 + continue; 100 + } 9 101 10 - fn part_1(_input: Self::Input) -> Option<String> { 102 + for (next_pos, _dir) in pos 103 + .adjacents_checked((71, 71)) 104 + .filter(|(p, _)| input.iter().take(i + 1).all(|op| op != p)) 105 + { 106 + let next_state = DState { 107 + cost: cost + 1, 108 + pos: next_pos, 109 + step_no: step_no + 1, 110 + }; 111 + if next_state.cost < *dist.get(&next_state.pos).unwrap_or(&usize::MAX) { 112 + *dist.entry(next_state.pos).or_insert(usize::MAX) = next_state.cost; 113 + queue.push(next_state); 114 + } 115 + } 116 + } 117 + if !flag { 118 + return Some(input[i].to_string()); 119 + } 120 + } 11 121 None 12 122 } 13 123 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 124 + fn parse_input(input: &str) -> Self::Input { 125 + input 126 + .trim() 127 + .lines() 128 + .map(|l| { 129 + let (x, y) = l.split_once(',').unwrap(); 130 + upos!(x.parse::<usize>().unwrap(), y.parse::<usize>().unwrap()) 131 + }) 132 + .collect() 16 133 } 17 134 }
+57 -7
years/2024/src/day_19.rs
··· 1 - 2 - use advent_core::{Day, day_stuff, ex_for_day}; 1 + use advent_core::{day_stuff, ex_for_day, Day}; 2 + use rayon::prelude::*; 3 + use std::collections::{HashMap, HashSet}; 3 4 4 5 pub struct Day19; 5 6 6 7 impl Day for Day19 { 8 + day_stuff!(19, "", "", (HashSet<String>, Vec<String>)); 7 9 8 - day_stuff!(19, "", ""); 10 + fn part_1(input: Self::Input) -> Option<String> { 11 + let (avail, desire) = input; 12 + let ans = desire 13 + .into_iter() 14 + .filter(|pat| { 15 + let pattern_ends = pat.len() + 1; 16 + let mut seen = HashMap::with_capacity(pattern_ends); 17 + seen.insert(0, 1); 18 + for end in 1..pattern_ends { 19 + for a in avail.iter().filter(|p| end >= p.len()) { 20 + let avail_start = end - a.len(); 21 + if &pat[avail_start..end] == a 22 + && let Some(avail_start) = seen.get(&avail_start).copied() 23 + { 24 + *seen.entry(end).or_insert(0) += avail_start; 25 + } 26 + } 27 + } 28 + seen.get(&pat.len()).copied().is_some_and(|v| v != 0) 29 + }) 30 + .count(); 9 31 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 32 + Some(ans.to_string()) 12 33 } 13 34 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 35 + fn part_2(input: Self::Input) -> Option<String> { 36 + let (avail, desire) = input; 37 + let ans = desire 38 + .into_par_iter() 39 + .map(|pat| { 40 + let pattern_ends = pat.len() + 1; 41 + let mut seen = HashMap::with_capacity(pattern_ends); 42 + seen.insert(0, 1); 43 + for end in 1..pattern_ends { 44 + for a in avail.iter().filter(|p| end >= p.len()) { 45 + let avail_start = end - a.len(); 46 + if &pat[avail_start..end] == a 47 + && let Some(avail_start) = seen.get(&avail_start).copied() 48 + { 49 + *seen.entry(end).or_insert(0) += avail_start; 50 + } 51 + } 52 + } 53 + seen.get(&pat.len()).copied().unwrap_or(0) 54 + }) 55 + .sum::<usize>(); 56 + 57 + Some(ans.to_string()) 58 + } 59 + 60 + fn parse_input(input: &str) -> Self::Input { 61 + let (avail, desire) = input.trim().split_once("\n\n").unwrap(); 62 + ( 63 + avail.split(", ").map(|s| s.to_string()).collect(), 64 + desire.split('\n').map(|s| s.to_string()).collect(), 65 + ) 16 66 } 17 67 }
+88 -6
years/2024/src/day_20.rs
··· 1 + use std::collections::HashMap; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 4 + use utils::{dir::CARDINALS, pos::Position, tiles}; 3 5 4 6 pub struct Day20; 5 7 8 + tiles!(Tile, [ 9 + '.' => Open, 10 + '#' => Wall, 11 + 'S' => Start, 12 + 'E' => End, 13 + ]); 14 + 15 + type Grid = utils::grid::Grid<Tile>; 16 + 6 17 impl Day for Day20 { 18 + day_stuff!(20, "", "", Grid); 7 19 8 - day_stuff!(20, "", ""); 20 + fn part_1(input: Self::Input) -> Option<String> { 21 + let end_pos = input.find_tile(&Tile::End).unwrap(); 22 + let start_pos = input.find_tile(&Tile::Start).unwrap(); 23 + let mut costs = HashMap::with_capacity(100); 24 + let mut curs = end_pos; 25 + let mut cost = 0; 26 + while curs != start_pos { 27 + costs.insert(curs, cost); 28 + let (_, next_pos, _) = input 29 + .relatives(curs, &CARDINALS) 30 + .filter(|(_, p, t)| **t != Tile::Wall && !costs.contains_key(p)) 31 + .next() 32 + .unwrap(); 33 + curs = next_pos; 34 + cost += 1; 35 + } 36 + costs.insert(start_pos, cost); 37 + 38 + let mut cheat_set = HashMap::<(Position, Position), usize>::with_capacity(costs.len()); 39 + for (pos_a, cost_a) in costs.iter() { 40 + for (pos_b, cost_b) in costs.iter() { 41 + if cost_b < cost_a { 42 + let diff = *cost_a - *cost_b; 43 + let dist = pos_a.manhattan(&pos_b).abs() as usize; 44 + if dist <= 2 { 45 + cheat_set.insert((*pos_a, *pos_b), diff - dist); 46 + } 47 + } 48 + } 49 + } 9 50 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 51 + let ans = cheat_set.values().filter(|c| **c >= 100).count(); 52 + 53 + dbg!(cheat_set.len()); 54 + 55 + Some(ans.to_string()) 12 56 } 13 57 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 58 + fn part_2(input: Self::Input) -> Option<String> { 59 + let end_pos = input.find_tile(&Tile::End).unwrap(); 60 + let start_pos = input.find_tile(&Tile::Start).unwrap(); 61 + let mut costs = HashMap::with_capacity(100); 62 + let mut curs = end_pos; 63 + let mut cost = 0; 64 + while curs != start_pos { 65 + costs.insert(curs, cost); 66 + let (_, next_pos, _) = input 67 + .relatives(curs, &CARDINALS) 68 + .filter(|(_, p, t)| **t != Tile::Wall && !costs.contains_key(p)) 69 + .next() 70 + .unwrap(); 71 + curs = next_pos; 72 + cost += 1; 73 + } 74 + costs.insert(start_pos, cost); 75 + 76 + let mut cheat_set = HashMap::<(Position, Position), usize>::with_capacity(costs.len()); 77 + for (pos_a, cost_a) in costs.iter() { 78 + for (pos_b, cost_b) in costs.iter() { 79 + if cost_b < cost_a { 80 + let diff = *cost_a - *cost_b; 81 + let dist = pos_a.manhattan(&pos_b).abs() as usize; 82 + if dist <= 20 { 83 + cheat_set.insert((*pos_a, *pos_b), diff - dist); 84 + } 85 + } 86 + } 87 + } 88 + 89 + let ans = cheat_set.values().filter(|c| **c >= 100).count(); 90 + 91 + dbg!(cheat_set.len()); 92 + 93 + Some(ans.to_string()) 94 + } 95 + 96 + fn parse_input(input: &str) -> Self::Input { 97 + Grid::parse(input) 16 98 } 17 99 }
+151 -6
years/2024/src/day_21.rs
··· 1 + use std::collections::{HashMap, VecDeque}; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 4 + use utils::{ 5 + dir::{Direction, CARDINALS}, 6 + pos::Position, 7 + }; 3 8 4 9 pub struct Day21; 5 10 11 + const NUMPAD: &str = "789\n456\n123\n#0A"; 12 + const DIRPAD: &str = "#^A\n<v>"; 13 + 14 + type Grid = utils::grid::Grid<char>; 15 + 16 + fn pad_grids() -> (Grid, Grid) { 17 + (Grid::parse(NUMPAD), Grid::parse(DIRPAD)) 18 + } 19 + 20 + fn dir_to_char(dir: Direction) -> char { 21 + match dir { 22 + Direction::East => '>', 23 + Direction::West => '<', 24 + Direction::North => '^', 25 + Direction::South => 'v', 26 + } 27 + } 28 + 29 + type BestMap = HashMap<(char, char), Vec<Vec<char>>>; 30 + 31 + fn find_best_paths(g: &Grid) -> BestMap { 32 + let mut costs = BestMap::with_capacity(18); 33 + 34 + for (pos1, tile1) in g.iter().filter(|(_, t)| **t != '#') { 35 + costs.insert((*tile1, *tile1), vec![vec![]]); 36 + for (pos2, tile2) in g.iter().filter(|(_, t)| **t != '#' && **t != *tile1) { 37 + let mut queue = VecDeque::<(Position, Vec<char>)>::with_capacity(18); 38 + queue.push_back((pos1, vec![])); 39 + while let Some((curr_pos, path)) = queue.pop_front() { 40 + if costs 41 + .get(&(*tile1, *tile2)) 42 + .is_some_and(|c| c[0].len() < path.len()) 43 + { 44 + continue; 45 + } 46 + 47 + if curr_pos == pos2 { 48 + (*costs.entry((*tile1, *tile2)).or_insert(vec![])).push(path); 49 + continue; 50 + } 51 + 52 + for (dir, new_pos, _) in g 53 + .relatives(curr_pos, &CARDINALS) 54 + .filter(|(_, _, t)| **t != '#' && **t != *tile1) 55 + { 56 + let mut new_path = path.clone(); 57 + new_path.push(dir_to_char(dir)); 58 + queue.push_back((new_pos, new_path)); 59 + } 60 + } 61 + } 62 + } 63 + 64 + costs 65 + .values_mut() 66 + .for_each(|v| v.iter_mut().for_each(|p| p.push('A'))); 67 + 68 + costs 69 + } 70 + 71 + fn recur_find( 72 + seq: &[char], 73 + level: usize, 74 + top: bool, 75 + robos: &mut Vec<char>, 76 + num_best: &BestMap, 77 + dir_best: &BestMap, 78 + dp: &mut HashMap<(Vec<char>, usize, char), usize>, 79 + ) -> usize { 80 + let key = (seq.to_vec(), level, robos[level]); 81 + if let Some(&res) = dp.get(&key) { 82 + return res; 83 + } 84 + 85 + let mut final_val = 0; 86 + 87 + for &c in seq { 88 + let all_paths = (if top { num_best } else { dir_best }) 89 + .get(&(robos[level], c)) 90 + .unwrap(); 91 + final_val += if level == 0 { 92 + all_paths.iter().map(Vec::len).min() 93 + } else { 94 + all_paths 95 + .iter() 96 + .map(|path| recur_find(path, level - 1, false, robos, num_best, dir_best, dp)) 97 + .min() 98 + } 99 + .unwrap(); 100 + robos[level] = c; 101 + } 102 + 103 + dp.insert(key, final_val); 104 + 105 + final_val 106 + } 107 + 6 108 impl Day for Day21 { 109 + day_stuff!(21, "", "", Vec<(usize, Vec<char>)>); 7 110 8 - day_stuff!(21, "", ""); 111 + fn part_1(input: Self::Input) -> Option<String> { 112 + let (num_grid, dir_grid) = pad_grids(); 113 + let (num_best, dir_best) = (find_best_paths(&num_grid), find_best_paths(&dir_grid)); 114 + let mut dp = HashMap::new(); 115 + let ans = input 116 + .into_iter() 117 + .map(|(num, code)| { 118 + let mut robos = vec!['A'; 3]; 119 + let best_path = 120 + recur_find(&code, 2, true, &mut robos, &num_best, &dir_best, &mut dp); 9 121 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 122 + best_path * num 123 + }) 124 + .sum::<usize>(); 125 + 126 + Some(ans.to_string()) 12 127 } 13 128 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 129 + fn part_2(input: Self::Input) -> Option<String> { 130 + let (num_grid, dir_grid) = pad_grids(); 131 + let (num_best, dir_best) = (find_best_paths(&num_grid), find_best_paths(&dir_grid)); 132 + let mut dp = HashMap::new(); 133 + let ans = input 134 + .into_iter() 135 + .map(|(num, code)| { 136 + let mut robos = vec!['A'; 26]; 137 + let best_path = 138 + recur_find(&code, 25, true, &mut robos, &num_best, &dir_best, &mut dp); 139 + 140 + best_path * num 141 + }) 142 + .sum::<usize>(); 143 + 144 + Some(ans.to_string()) 145 + } 146 + 147 + fn parse_input(input: &str) -> Self::Input { 148 + input 149 + .trim() 150 + .lines() 151 + .map(|l| { 152 + ( 153 + l.trim_start_matches('0') 154 + .trim_end_matches('A') 155 + .parse::<usize>() 156 + .unwrap(), 157 + l.chars().collect(), 158 + ) 159 + }) 160 + .collect() 16 161 } 17 162 }
+77 -6
years/2024/src/day_22.rs
··· 1 + use std::collections::{HashMap, HashSet}; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 3 4 4 5 pub struct Day22; 5 6 7 + fn next_secret(mut num: usize) -> usize { 8 + num = ((num * 64) ^ num) % 16777216; 9 + num = ((num / 32) ^ num) % 16777216; 10 + num = ((num * 2048) ^ num) % 16777216; 11 + num 12 + } 13 + 14 + fn secret_n_times(init: usize, times: usize) -> usize { 15 + let mut num = init; 16 + for _ in 0..times { 17 + num = next_secret(num); 18 + } 19 + num 20 + } 21 + 22 + fn get_all_four_unique_changes(init: usize, times: usize) -> HashMap<[isize; 4], usize> { 23 + let mut last = 0; 24 + let mut changes = Vec::with_capacity(times + 1); 25 + let mut curr = init; 26 + for _ in 0..times { 27 + curr = next_secret(curr); 28 + let val = curr % 10; 29 + changes.push(((val as isize) - (last as isize), val)); 30 + last = val; 31 + } 32 + 33 + changes 34 + .windows(4) 35 + .fold(HashMap::with_capacity(times / 4), |mut acc, w| { 36 + let changes = [w[0].0, w[1].0, w[2].0, w[3].0]; 37 + let final_val = w[3].1; 38 + if !acc.contains_key(&changes) { 39 + acc.insert(changes, final_val); 40 + } 41 + acc 42 + }) 43 + } 44 + 6 45 impl Day for Day22 { 46 + day_stuff!(22, "", "", Vec<usize>); 7 47 8 - day_stuff!(22, "", ""); 48 + fn part_1(input: Self::Input) -> Option<String> { 49 + let ans = input 50 + .into_iter() 51 + .map(|init| secret_n_times(init, 2000)) 52 + .sum::<usize>(); 53 + 54 + Some(ans.to_string()) 55 + } 56 + 57 + fn part_2(input: Self::Input) -> Option<String> { 58 + let change_to_val = input 59 + .into_iter() 60 + .map(|init| get_all_four_unique_changes(init, 2000)) 61 + .collect::<Vec<_>>(); 62 + let all_changes = change_to_val 63 + .iter() 64 + .flat_map(|h| h.keys().copied()) 65 + .collect::<HashSet<_>>(); 66 + 67 + let ans = all_changes 68 + .into_iter() 69 + .map(|c| { 70 + change_to_val 71 + .iter() 72 + .map(|h| h.get(&c).unwrap_or(&0)) 73 + .sum::<usize>() 74 + }) 75 + .max() 76 + .unwrap(); 9 77 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 78 + Some(ans.to_string()) 12 79 } 13 80 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 81 + fn parse_input(input: &str) -> Self::Input { 82 + input 83 + .trim() 84 + .lines() 85 + .map(|l| l.parse::<usize>().unwrap()) 86 + .collect() 16 87 } 17 88 }