Advent of Code solutions

Day 12,13,14

bwc9876.dev ad295a7e 56ee21ce

verified
+424 -22
+4
.gitignore
··· 1 1 target 2 2 scratch/ 3 3 result 4 + 5 + # For 2024 Day 14 - I am in hell 6 + trees*.txt 7 +
+1 -1
macros/src/lib.rs
··· 6 6 7 7 fn make_day_mods() -> String { 8 8 (1..=MAX_DAY) 9 - .map(|day| format!("mod day_{day};", day = day)) 9 + .map(|day| format!("pub mod day_{day};", day = day)) 10 10 .collect::<Vec<_>>() 11 11 .join("\n") 12 12 }
+4
utils/src/dir.rs
··· 127 127 (Self::West, false) => Self::South, 128 128 } 129 129 } 130 + 131 + pub fn is_horizontal(&self) -> bool { 132 + matches!(self, Direction::East | Direction::West) 133 + } 130 134 } 131 135 132 136 impl From<Position> for Direction {
+9
utils/src/grid.rs
··· 434 434 .filter_map(move |(pos, dir)| self.get(pos).map(|v| (dir, pos, v))) 435 435 } 436 436 437 + pub fn relatives_valid<'a, M: Movement>( 438 + &'a self, 439 + pos: Position, 440 + kernels: &'a [M], 441 + ) -> impl Iterator<Item = (M, Option<(Position, &'a T)>)> + 'a { 442 + pos.relatives(kernels) 443 + .map(move |(pos, dir)| (dir, self.get(pos).map(|v| (pos, v)))) 444 + } 445 + 437 446 /// Get all positions relative to the given position, returning [None] if the relatives would 438 447 /// go out of bounds 439 448 ///
+33
years/2024/src/da_tree.txt
··· 1 + .*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.* 2 + .*X_____________________________X.* 3 + .*X_____________________________X.* 4 + .*X_____________________________X.* 5 + .*X_____________________________X.* 6 + .*X______________X______________X.* 7 + .*X_____________XXX_____________X.* 8 + .*X____________XXXXX____________X.* 9 + .*X___________XXXXXXX___________X.* 10 + .*X__________XXXXXXXXX__________X.* 11 + .*X____________XXXXX____________X.* 12 + .*X___________XXXXXXX___________X.* 13 + .*X__________XXXXXXXXX__________X.* 14 + .*X_________XXXXXXXXXXX_________X.* 15 + .*X________XXXXXXXXXXXXX________X.* 16 + .*X__________XXXXXXXXX__________X.* 17 + .*X_________XXXXXXXXXXX_________X.* 18 + .*X________XXXXXXXXXXXXX________X.* 19 + .*X_______XXXXXXXXXXXXXXX_______X.* 20 + .*X______XXXXXXXXXXXXXXXXX______X.* 21 + .*X________XXXXXXXXXXXXX________X.* 22 + .*X_______XXXXXXXXXXXXXXX_______X.* 23 + .*X______XXXXXXXXXXXXXXXXX______X.* 24 + .*X_____XXXXXXXXXXXXXXXXXXX_____X.* 25 + .*X____XXXXXXXXXXXXXXXXXXXXX____X.* 26 + .*X_____________XXX_____________X.* 27 + .*X_____________XXX_____________X.* 28 + .*X_____________XXX_____________X.* 29 + .*X_____________________________X.* 30 + .*X_____________________________X.* 31 + .*X_____________________________X.* 32 + .*X_____________________________X.* 33 + .*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.*
+2 -2
years/2024/src/day_11.rs
··· 5 5 6 6 pub struct Day11; 7 7 8 - fn do_blinks(stones: Vec<usize>, blinks: usize) -> usize { 8 + pub fn do_blinks(stones: Vec<usize>, blinks: usize) -> usize { 9 9 let l = stones.len(); 10 10 let mut stone_map = stones 11 11 .into_iter() 12 12 .fold(HashMap::with_capacity(l), |mut acc, stone| { 13 - acc.entry(stone).and_modify(|c| *c += 1).or_insert(1); 13 + *acc.entry(stone).or_insert(0) += 1; 14 14 acc 15 15 }); 16 16
+170 -6
years/2024/src/day_12.rs
··· 1 + use std::collections::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, CARDINALS}, 6 + pos::Position, 7 + prelude::GridCursor, 8 + }; 3 9 4 10 pub struct Day12; 5 11 12 + type Grid = utils::grid::Grid<char>; 13 + 14 + fn trace_perim( 15 + grid: &Grid, 16 + starting_pos: Position, 17 + initial_dir: Direction, 18 + c: char, 19 + ) -> (usize, HashSet<Position>) { 20 + let mut perims = HashSet::with_capacity(100); 21 + let mut turns = 0; 22 + let mut curs = GridCursor::new(&grid, starting_pos, initial_dir); 23 + let mut init = false; 24 + 25 + while (curs.pos, curs.dir) != (starting_pos, initial_dir) || !init { 26 + perims.insert(curs.pos); 27 + 28 + let look_left = curs.pos.add(&curs.dir.ninety_deg(false).get_kernel()); 29 + 30 + if grid.get(look_left).is_some_and(|c2| *c2 == c) { 31 + // Interior corner, turn left! 32 + curs.turn(false); 33 + curs.move_forward(); 34 + turns += 1; 35 + } else if curs.peek_forward().is_none_or(|(_, c2)| *c2 != c) { 36 + // Exterior corner, turn right! 37 + curs.turn(true); 38 + turns += 1; 39 + } else { 40 + // Valid, keep going! 41 + curs.move_forward(); 42 + } 43 + 44 + init = true; 45 + } 46 + 47 + (turns, perims) 48 + } 49 + 6 50 impl Day for Day12 { 51 + fn part_1(input: Self::Input) -> Option<String> { 52 + let mut visited = HashSet::with_capacity(50); 53 + let mut total = 0; 7 54 8 - day_stuff!(12, "", ""); 55 + for (pos, c) in input.iter() { 56 + if !visited.contains(&pos) { 57 + let mut flood = vec![pos]; 9 58 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 59 + let (mut area, mut perim) = (0, 0); 60 + 61 + while !flood.is_empty() { 62 + let mut next = vec![]; 63 + 64 + for pos2 in flood.iter() { 65 + if let Some(c2) = input.get(*pos2) 66 + && c2 == c 67 + && !visited.contains(pos2) 68 + { 69 + area += 1; 70 + let mut rel = input 71 + .relatives(*pos2, &CARDINALS) 72 + .filter_map( 73 + |(_, pos3, c3)| if c2 == c3 { Some(pos3) } else { None }, 74 + ) 75 + .collect::<Vec<_>>(); 76 + 77 + if rel.len() != 4 { 78 + perim += 4 - rel.len(); 79 + } 80 + 81 + visited.insert(*pos2); 82 + next.append(&mut rel); 83 + } 84 + } 85 + 86 + flood = next; 87 + } 88 + 89 + println!("{c} @ {pos}: area = {area}; perim = {perim};"); 90 + total += area * perim; 91 + 92 + visited.insert(pos); 93 + } 94 + } 95 + 96 + Some(total.to_string()) 12 97 } 13 98 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 99 + // TODO: Still working on the ""nice"" way of doing this one 100 + fn part_2(input: Self::Input) -> Option<String> { 101 + let mut visited = HashSet::with_capacity(50); 102 + let mut shapes = Vec::<(usize, usize, HashSet<Position>)>::with_capacity(30); 103 + 104 + for (pos, c) in input.iter() { 105 + if !visited.contains(&pos) { 106 + let (turns, perimeters) = trace_perim(&input, pos, Direction::East, *c); 107 + 108 + let mut all_tiles = HashSet::with_capacity(turns); 109 + 110 + let mut flood = vec![pos]; 111 + 112 + while !flood.is_empty() { 113 + let mut next = vec![]; 114 + 115 + for pos2 in flood.iter() { 116 + if let Some(c2) = input.get(*pos2) 117 + && c2 == c 118 + && !visited.contains(pos2) 119 + { 120 + all_tiles.insert(*pos2); 121 + 122 + let mut rel = input 123 + .relatives(*pos2, &CARDINALS) 124 + .filter_map( 125 + |(_, pos3, c3)| if c2 == c3 { Some(pos3) } else { None }, 126 + ) 127 + .collect::<Vec<_>>(); 128 + 129 + if !perimeters.contains(pos2) { 130 + let _inner_shape = if input 131 + .get(pos2.add(&Direction::East.get_kernel())) 132 + .is_some_and(|c2| c2 != c) 133 + { 134 + Some(trace_perim(&input, *pos2, Direction::North, *c)) 135 + } else if input 136 + .get(pos2.add(&Direction::North.get_kernel())) 137 + .is_some_and(|c2| c2 != c) 138 + { 139 + Some(trace_perim(&input, *pos2, Direction::East, *c)) 140 + } else { 141 + None 142 + }; 143 + } 144 + 145 + visited.insert(*pos2); 146 + next.append(&mut rel); 147 + } 148 + } 149 + 150 + flood = next; 151 + } 152 + 153 + shapes.push((all_tiles.len(), turns, all_tiles)) 154 + } 155 + } 156 + 157 + let _ = shapes 158 + .iter() 159 + .inspect(|(a, s, p)| { 160 + println!( 161 + "{:?}: {a}; {s}", 162 + input.get(*p.iter().next().unwrap()).unwrap() 163 + ) 164 + }) 165 + .collect::<Vec<_>>(); 166 + 167 + Some( 168 + shapes 169 + .into_iter() 170 + .map(|(area, turns, _)| area * turns) 171 + .sum::<usize>() 172 + .to_string(), 173 + ) 174 + } 175 + 176 + day_stuff!(12, "", "", Grid); 177 + 178 + fn parse_input(input: &str) -> Self::Input { 179 + Grid::parse(input.trim()) 16 180 } 17 181 }
+97 -7
years/2024/src/day_13.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 Day13; 4 + 5 + #[derive(Debug, Clone, Copy)] 6 + pub struct Machine { 7 + a: (isize, isize), 8 + b: (isize, isize), 9 + goal: (isize, isize), 10 + } 3 11 4 - pub struct Day13; 12 + impl Machine { 13 + pub fn parse(raw: &str) -> Self { 14 + let mut l = raw.lines(); 15 + let (ax, ay) = l 16 + .next() 17 + .unwrap() 18 + .split_once(": ") 19 + .unwrap() 20 + .1 21 + .split_once(", ") 22 + .unwrap(); 23 + let (bx, by) = l 24 + .next() 25 + .unwrap() 26 + .split_once(": ") 27 + .unwrap() 28 + .1 29 + .split_once(", ") 30 + .unwrap(); 31 + let (ax, ay) = ( 32 + ax.split_once("+").unwrap().1.parse::<isize>().unwrap(), 33 + ay.split_once("+").unwrap().1.parse::<isize>().unwrap(), 34 + ); 35 + 36 + let (bx, by) = ( 37 + bx.split_once("+").unwrap().1.parse::<isize>().unwrap(), 38 + by.split_once("+").unwrap().1.parse::<isize>().unwrap(), 39 + ); 40 + 41 + let (px, py) = l 42 + .next() 43 + .unwrap() 44 + .split_once(": ") 45 + .unwrap() 46 + .1 47 + .split_once(", ") 48 + .unwrap(); 49 + let (px, py) = ( 50 + px.split_once("=").unwrap().1.parse::<isize>().unwrap(), 51 + py.split_once("=").unwrap().1.parse::<isize>().unwrap(), 52 + ); 53 + 54 + Self { 55 + a: (ax, ay), 56 + b: (bx, by), 57 + goal: (px, py), 58 + } 59 + } 60 + 61 + pub fn presses_needed_for_prizes(&self) -> Option<isize> { 62 + let a_presses = (self.b.0 * self.goal.1 - self.b.1 * self.goal.0) as f64 63 + / (self.b.0 * self.a.1 - self.b.1 * self.a.0) as f64; 64 + 65 + let b_left = self.goal.0 as f64 - self.a.0 as f64 * a_presses; 66 + let b_presses = b_left / self.b.0 as f64; 67 + 68 + if a_presses % 1.0 == 0.0 && b_presses % 1.0 == 0.0 { 69 + Some((3.0 * a_presses + b_presses) as isize) 70 + } else { 71 + None 72 + } 73 + } 74 + } 5 75 6 76 impl Day for Day13 { 77 + day_stuff!(13, "", "", Vec<Machine>); 7 78 8 - day_stuff!(13, "", ""); 79 + fn part_1(input: Self::Input) -> Option<String> { 80 + Some( 81 + input 82 + .iter() 83 + .filter_map(Machine::presses_needed_for_prizes) 84 + .sum::<isize>() 85 + .to_string(), 86 + ) 87 + } 9 88 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 89 + fn part_2(mut input: Self::Input) -> Option<String> { 90 + input.iter_mut().for_each(|m| { 91 + m.goal.0 += 10000000000000; 92 + m.goal.1 += 10000000000000; 93 + }); 94 + 95 + Some( 96 + input 97 + .iter() 98 + .filter_map(Machine::presses_needed_for_prizes) 99 + .sum::<isize>() 100 + .to_string(), 101 + ) 12 102 } 13 103 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 104 + fn parse_input(input: &str) -> Self::Input { 105 + input.split("\n\n").map(Machine::parse).collect() 16 106 } 17 107 }
+104 -6
years/2024/src/day_14.rs
··· 1 + use std::{cmp::Ordering, collections::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 regex::Regex; 5 + use utils::{ipos, pos::Position}; 3 6 4 7 pub struct Day14; 5 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 + ) 17 + } 18 + 6 19 impl Day for Day14 { 20 + day_stuff!(14, "", "", Vec<(Position, Position)>); 7 21 8 - day_stuff!(14, "", ""); 22 + fn part_1(input: Self::Input) -> Option<String> { 23 + let bounds = Position::new(101, 103); 24 + let times = 100; 25 + let (ur, ul, ll, lr) = input 26 + .into_iter() 27 + .map(move |(pos, vel)| robot_go(pos, vel, times, bounds)) 28 + .fold((0, 0, 0, 0), move |mut acc, robo| { 29 + let is_upper = match robo.y.cmp(&(bounds.y / 2)) { 30 + Ordering::Equal => None, 31 + Ordering::Greater => Some(false), 32 + Ordering::Less => Some(true), 33 + }; 34 + let is_left = match robo.x.cmp(&(bounds.x / 2)) { 35 + Ordering::Equal => None, 36 + Ordering::Greater => Some(false), 37 + Ordering::Less => Some(true), 38 + }; 39 + if let (Some(is_upper), Some(is_left)) = (is_upper, is_left) { 40 + let to_inc = match (is_upper, is_left) { 41 + (true, true) => &mut acc.1, 42 + (true, false) => &mut acc.0, 43 + (false, true) => &mut acc.2, 44 + (false, false) => &mut acc.3, 45 + }; 46 + 47 + *to_inc += 1; 48 + } 49 + acc 50 + }); 9 51 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 52 + Some((dbg!(ur) * dbg!(ul) * dbg!(ll) * dbg!(lr)).to_string()) 12 53 } 13 54 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 55 + fn part_2(input: Self::Input) -> Option<String> { 56 + let bounds = Position::new(101, 103); 57 + 58 + let re = Regex::new(include_str!("da_tree.txt")).unwrap(); 59 + 60 + for i in 0..i32::MAX { 61 + let bots = input 62 + .iter() 63 + .map(move |r| robot_go(r.0, r.1, i as isize, bounds)) 64 + .collect::<HashSet<_>>(); 65 + 66 + let hay = (0..bounds.y) 67 + .flat_map(|y| { 68 + let bots = &bots; 69 + (0..bounds.x) 70 + .map(move |x| { 71 + let pos = Position::new(x, y); 72 + if bots.contains(&pos) { 73 + 'X' 74 + } else { 75 + '_' 76 + } 77 + }) 78 + .chain(['\n']) 79 + }) 80 + .collect::<String>(); 81 + 82 + if re.is_match(&hay) { 83 + return Some(i.to_string()); 84 + } 85 + } 86 + 87 + Some("FUCK".to_string()) 88 + } 89 + 90 + fn parse_input(input: &str) -> Self::Input { 91 + input 92 + .trim() 93 + .lines() 94 + .map(|l| { 95 + let (pr, vr) = l.split_once(" ").unwrap(); 96 + let p = pr 97 + .split_once("=") 98 + .unwrap() 99 + .1 100 + .split(",") 101 + .map(|s| s.parse::<isize>().unwrap()) 102 + .collect::<Vec<_>>(); 103 + let v = vr 104 + .split_once("=") 105 + .unwrap() 106 + .1 107 + .split(",") 108 + .map(|s| s.parse::<isize>().unwrap()) 109 + .collect::<Vec<_>>(); 110 + 111 + (Position::new(p[0], p[1]), Position::new(v[0], v[1])) 112 + }) 113 + .collect() 16 114 } 17 115 }