diff --git a/.gitignore b/.gitignore index f280c7f..78f0a78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ target scratch/ result + +# For 2024 Day 14 - I am in hell +trees*.txt + diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 684c8bc..3c10113 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::TokenStream; fn make_day_mods() -> String { (1..=MAX_DAY) - .map(|day| format!("mod day_{day};", day = day)) + .map(|day| format!("pub mod day_{day};", day = day)) .collect::>() .join("\n") } diff --git a/utils/src/dir.rs b/utils/src/dir.rs index b8ad38c..f785eda 100644 --- a/utils/src/dir.rs +++ b/utils/src/dir.rs @@ -127,6 +127,10 @@ impl Direction { (Self::West, false) => Self::South, } } + + pub fn is_horizontal(&self) -> bool { + matches!(self, Direction::East | Direction::West) + } } impl From for Direction { diff --git a/utils/src/grid.rs b/utils/src/grid.rs index 80a1aa7..ed1781c 100644 --- a/utils/src/grid.rs +++ b/utils/src/grid.rs @@ -434,6 +434,15 @@ impl Grid { .filter_map(move |(pos, dir)| self.get(pos).map(|v| (dir, pos, v))) } + pub fn relatives_valid<'a, M: Movement>( + &'a self, + pos: Position, + kernels: &'a [M], + ) -> impl Iterator)> + 'a { + pos.relatives(kernels) + .map(move |(pos, dir)| (dir, self.get(pos).map(|v| (pos, v)))) + } + /// Get all positions relative to the given position, returning [None] if the relatives would /// go out of bounds /// diff --git a/years/2024/src/da_tree.txt b/years/2024/src/da_tree.txt new file mode 100644 index 0000000..3f581fa --- /dev/null +++ b/years/2024/src/da_tree.txt @@ -0,0 +1,33 @@ +.*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.* +.*X_____________________________X.* +.*X_____________________________X.* +.*X_____________________________X.* +.*X_____________________________X.* +.*X______________X______________X.* +.*X_____________XXX_____________X.* +.*X____________XXXXX____________X.* +.*X___________XXXXXXX___________X.* +.*X__________XXXXXXXXX__________X.* +.*X____________XXXXX____________X.* +.*X___________XXXXXXX___________X.* +.*X__________XXXXXXXXX__________X.* +.*X_________XXXXXXXXXXX_________X.* +.*X________XXXXXXXXXXXXX________X.* +.*X__________XXXXXXXXX__________X.* +.*X_________XXXXXXXXXXX_________X.* +.*X________XXXXXXXXXXXXX________X.* +.*X_______XXXXXXXXXXXXXXX_______X.* +.*X______XXXXXXXXXXXXXXXXX______X.* +.*X________XXXXXXXXXXXXX________X.* +.*X_______XXXXXXXXXXXXXXX_______X.* +.*X______XXXXXXXXXXXXXXXXX______X.* +.*X_____XXXXXXXXXXXXXXXXXXX_____X.* +.*X____XXXXXXXXXXXXXXXXXXXXX____X.* +.*X_____________XXX_____________X.* +.*X_____________XXX_____________X.* +.*X_____________XXX_____________X.* +.*X_____________________________X.* +.*X_____________________________X.* +.*X_____________________________X.* +.*X_____________________________X.* +.*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.* diff --git a/years/2024/src/day_11.rs b/years/2024/src/day_11.rs index 9c918bb..429de95 100644 --- a/years/2024/src/day_11.rs +++ b/years/2024/src/day_11.rs @@ -5,12 +5,12 @@ use utils::num::{num_digits, split_num_once}; pub struct Day11; -fn do_blinks(stones: Vec, blinks: usize) -> usize { +pub fn do_blinks(stones: Vec, blinks: usize) -> usize { let l = stones.len(); let mut stone_map = stones .into_iter() .fold(HashMap::with_capacity(l), |mut acc, stone| { - acc.entry(stone).and_modify(|c| *c += 1).or_insert(1); + *acc.entry(stone).or_insert(0) += 1; acc }); diff --git a/years/2024/src/day_12.rs b/years/2024/src/day_12.rs index 9c1d59f..47552e2 100644 --- a/years/2024/src/day_12.rs +++ b/years/2024/src/day_12.rs @@ -1,17 +1,181 @@ +use std::collections::HashSet; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use utils::{ + dir::{Direction, Movement, CARDINALS}, + pos::Position, + prelude::GridCursor, +}; pub struct Day12; +type Grid = utils::grid::Grid; + +fn trace_perim( + grid: &Grid, + starting_pos: Position, + initial_dir: Direction, + c: char, +) -> (usize, HashSet) { + let mut perims = HashSet::with_capacity(100); + let mut turns = 0; + let mut curs = GridCursor::new(&grid, starting_pos, initial_dir); + let mut init = false; + + while (curs.pos, curs.dir) != (starting_pos, initial_dir) || !init { + perims.insert(curs.pos); + + let look_left = curs.pos.add(&curs.dir.ninety_deg(false).get_kernel()); + + if grid.get(look_left).is_some_and(|c2| *c2 == c) { + // Interior corner, turn left! + curs.turn(false); + curs.move_forward(); + turns += 1; + } else if curs.peek_forward().is_none_or(|(_, c2)| *c2 != c) { + // Exterior corner, turn right! + curs.turn(true); + turns += 1; + } else { + // Valid, keep going! + curs.move_forward(); + } + + init = true; + } + + (turns, perims) +} + impl Day for Day12 { + fn part_1(input: Self::Input) -> Option { + let mut visited = HashSet::with_capacity(50); + let mut total = 0; + + for (pos, c) in input.iter() { + if !visited.contains(&pos) { + let mut flood = vec![pos]; + + let (mut area, mut perim) = (0, 0); + + while !flood.is_empty() { + let mut next = vec![]; + + for pos2 in flood.iter() { + if let Some(c2) = input.get(*pos2) + && c2 == c + && !visited.contains(pos2) + { + area += 1; + let mut rel = input + .relatives(*pos2, &CARDINALS) + .filter_map( + |(_, pos3, c3)| if c2 == c3 { Some(pos3) } else { None }, + ) + .collect::>(); + + if rel.len() != 4 { + perim += 4 - rel.len(); + } + + visited.insert(*pos2); + next.append(&mut rel); + } + } + + flood = next; + } - day_stuff!(12, "", ""); + println!("{c} @ {pos}: area = {area}; perim = {perim};"); + total += area * perim; - fn part_1(_input: Self::Input) -> Option { - None + visited.insert(pos); + } + } + + Some(total.to_string()) + } + + // TODO: Still working on the ""nice"" way of doing this one + fn part_2(input: Self::Input) -> Option { + let mut visited = HashSet::with_capacity(50); + let mut shapes = Vec::<(usize, usize, HashSet)>::with_capacity(30); + + for (pos, c) in input.iter() { + if !visited.contains(&pos) { + let (turns, perimeters) = trace_perim(&input, pos, Direction::East, *c); + + let mut all_tiles = HashSet::with_capacity(turns); + + let mut flood = vec![pos]; + + while !flood.is_empty() { + let mut next = vec![]; + + for pos2 in flood.iter() { + if let Some(c2) = input.get(*pos2) + && c2 == c + && !visited.contains(pos2) + { + all_tiles.insert(*pos2); + + let mut rel = input + .relatives(*pos2, &CARDINALS) + .filter_map( + |(_, pos3, c3)| if c2 == c3 { Some(pos3) } else { None }, + ) + .collect::>(); + + if !perimeters.contains(pos2) { + let _inner_shape = if input + .get(pos2.add(&Direction::East.get_kernel())) + .is_some_and(|c2| c2 != c) + { + Some(trace_perim(&input, *pos2, Direction::North, *c)) + } else if input + .get(pos2.add(&Direction::North.get_kernel())) + .is_some_and(|c2| c2 != c) + { + Some(trace_perim(&input, *pos2, Direction::East, *c)) + } else { + None + }; + } + + visited.insert(*pos2); + next.append(&mut rel); + } + } + + flood = next; + } + + shapes.push((all_tiles.len(), turns, all_tiles)) + } + } + + let _ = shapes + .iter() + .inspect(|(a, s, p)| { + println!( + "{:?}: {a}; {s}", + input.get(*p.iter().next().unwrap()).unwrap() + ) + }) + .collect::>(); + + Some( + shapes + .into_iter() + .map(|(area, turns, _)| area * turns) + .sum::() + .to_string(), + ) } - fn part_2(_input: Self::Input) -> Option { - None + day_stuff!(12, "", "", Grid); + + fn parse_input(input: &str) -> Self::Input { + Grid::parse(input.trim()) } } diff --git a/years/2024/src/day_13.rs b/years/2024/src/day_13.rs index af89ed5..9b4e872 100644 --- a/years/2024/src/day_13.rs +++ b/years/2024/src/day_13.rs @@ -1,17 +1,107 @@ - -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; pub struct Day13; +#[derive(Debug, Clone, Copy)] +pub struct Machine { + a: (isize, isize), + b: (isize, isize), + goal: (isize, isize), +} + +impl Machine { + pub fn parse(raw: &str) -> Self { + let mut l = raw.lines(); + let (ax, ay) = l + .next() + .unwrap() + .split_once(": ") + .unwrap() + .1 + .split_once(", ") + .unwrap(); + let (bx, by) = l + .next() + .unwrap() + .split_once(": ") + .unwrap() + .1 + .split_once(", ") + .unwrap(); + let (ax, ay) = ( + ax.split_once("+").unwrap().1.parse::().unwrap(), + ay.split_once("+").unwrap().1.parse::().unwrap(), + ); + + let (bx, by) = ( + bx.split_once("+").unwrap().1.parse::().unwrap(), + by.split_once("+").unwrap().1.parse::().unwrap(), + ); + + let (px, py) = l + .next() + .unwrap() + .split_once(": ") + .unwrap() + .1 + .split_once(", ") + .unwrap(); + let (px, py) = ( + px.split_once("=").unwrap().1.parse::().unwrap(), + py.split_once("=").unwrap().1.parse::().unwrap(), + ); + + Self { + a: (ax, ay), + b: (bx, by), + goal: (px, py), + } + } + + pub fn presses_needed_for_prizes(&self) -> Option { + let a_presses = (self.b.0 * self.goal.1 - self.b.1 * self.goal.0) as f64 + / (self.b.0 * self.a.1 - self.b.1 * self.a.0) as f64; + + let b_left = self.goal.0 as f64 - self.a.0 as f64 * a_presses; + let b_presses = b_left / self.b.0 as f64; + + if a_presses % 1.0 == 0.0 && b_presses % 1.0 == 0.0 { + Some((3.0 * a_presses + b_presses) as isize) + } else { + None + } + } +} + impl Day for Day13 { + day_stuff!(13, "", "", Vec); + + fn part_1(input: Self::Input) -> Option { + Some( + input + .iter() + .filter_map(Machine::presses_needed_for_prizes) + .sum::() + .to_string(), + ) + } - day_stuff!(13, "", ""); + fn part_2(mut input: Self::Input) -> Option { + input.iter_mut().for_each(|m| { + m.goal.0 += 10000000000000; + m.goal.1 += 10000000000000; + }); - fn part_1(_input: Self::Input) -> Option { - None + Some( + input + .iter() + .filter_map(Machine::presses_needed_for_prizes) + .sum::() + .to_string(), + ) } - fn part_2(_input: Self::Input) -> Option { - None + fn parse_input(input: &str) -> Self::Input { + input.split("\n\n").map(Machine::parse).collect() } } diff --git a/years/2024/src/day_14.rs b/years/2024/src/day_14.rs index 87dad43..2c6d001 100644 --- a/years/2024/src/day_14.rs +++ b/years/2024/src/day_14.rs @@ -1,17 +1,115 @@ +use std::{cmp::Ordering, collections::HashSet}; -use advent_core::{Day, day_stuff, ex_for_day}; +use advent_core::{day_stuff, ex_for_day, Day}; +use regex::Regex; +use utils::{ipos, pos::Position}; 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 } + ) +} + impl Day for Day14 { + day_stuff!(14, "", "", Vec<(Position, Position)>); + + fn part_1(input: Self::Input) -> Option { + let bounds = Position::new(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)) { + Ordering::Equal => None, + Ordering::Greater => Some(false), + Ordering::Less => Some(true), + }; + let is_left = match robo.x.cmp(&(bounds.x / 2)) { + Ordering::Equal => None, + Ordering::Greater => Some(false), + Ordering::Less => Some(true), + }; + if let (Some(is_upper), Some(is_left)) = (is_upper, is_left) { + let to_inc = match (is_upper, is_left) { + (true, true) => &mut acc.1, + (true, false) => &mut acc.0, + (false, true) => &mut acc.2, + (false, false) => &mut acc.3, + }; - day_stuff!(14, "", ""); + *to_inc += 1; + } + acc + }); - fn part_1(_input: Self::Input) -> Option { - None + Some((dbg!(ur) * dbg!(ul) * dbg!(ll) * dbg!(lr)).to_string()) } - fn part_2(_input: Self::Input) -> Option { - None + fn part_2(input: Self::Input) -> Option { + let bounds = Position::new(101, 103); + + let re = Regex::new(include_str!("da_tree.txt")).unwrap(); + + for i in 0..i32::MAX { + let bots = input + .iter() + .map(move |r| robot_go(r.0, r.1, i as isize, bounds)) + .collect::>(); + + let hay = (0..bounds.y) + .flat_map(|y| { + let bots = &bots; + (0..bounds.x) + .map(move |x| { + let pos = Position::new(x, y); + if bots.contains(&pos) { + 'X' + } else { + '_' + } + }) + .chain(['\n']) + }) + .collect::(); + + if re.is_match(&hay) { + return Some(i.to_string()); + } + } + + Some("FUCK".to_string()) + } + + fn parse_input(input: &str) -> Self::Input { + input + .trim() + .lines() + .map(|l| { + let (pr, vr) = l.split_once(" ").unwrap(); + let p = pr + .split_once("=") + .unwrap() + .1 + .split(",") + .map(|s| s.parse::().unwrap()) + .collect::>(); + let v = vr + .split_once("=") + .unwrap() + .1 + .split(",") + .map(|s| s.parse::().unwrap()) + .collect::>(); + + (Position::new(p[0], p[1]), Position::new(v[0], v[1])) + }) + .collect() } }