diff --git a/README.md b/README.md index 7adb3df2..098127a5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,6 @@ Development occurs in language-specific directories: |[Day12.hs](hs/src/Day12.hs)|[Day12.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day12.kt)|[day12.py](py/aoc2024/day12.py)|[day12.rs](rs/src/day12.rs)| |[Day13.hs](hs/src/Day13.hs)|[Day13.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day13.kt)|[day13.py](py/aoc2024/day13.py)|[day13.rs](rs/src/day13.rs)| |[Day14.hs](hs/src/Day14.hs)|[Day14.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day14.kt)|[day14.py](py/aoc2024/day14.py)|[day14.rs](rs/src/day14.rs)| -|[Day15.hs](hs/src/Day15.hs)|[Day15.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day15.kt)|[day15.py](py/aoc2024/day15.py)|| +|[Day15.hs](hs/src/Day15.hs)|[Day15.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day15.kt)|[day15.py](py/aoc2024/day15.py)|[day15.rs](rs/src/day15.rs)| |[Day16.hs](hs/src/Day16.hs)|[Day16.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day16.kt)|[day16.py](py/aoc2024/day16.py)|| |[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day17.kt)||| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index a6040b68..0c7c1fc0 100644 --- a/rs/benches/criterion.rs +++ b/rs/benches/criterion.rs @@ -1,5 +1,5 @@ use aoc2024::{ - day1, day10, day11, day12, day13, day14, day2, day3, day4, day5, day6, day7, day8, day9, + day1, day10, day11, day12, day13, day14, day15, day2, day3, day4, day5, day6, day7, day8, day9, }; use criterion::{black_box, Criterion}; use std::env; @@ -100,6 +100,12 @@ fn aoc2024_bench(c: &mut Criterion) -> io::Result<()> { g.bench_function("part 2", |b| b.iter(|| day14::part2(black_box(&data)))); g.finish(); + let data = get_day_input(15)?; + let mut g = c.benchmark_group("day 15"); + g.bench_function("part 1", |b| b.iter(|| day15::part1(black_box(&data)))); + g.bench_function("part 2", |b| b.iter(|| day15::part2(black_box(&data)))); + g.finish(); + Ok(()) } diff --git a/rs/src/day15.rs b/rs/src/day15.rs new file mode 100644 index 00000000..30982c28 --- /dev/null +++ b/rs/src/day15.rs @@ -0,0 +1,189 @@ +use std::collections::BTreeSet; +use std::ops::IndexMut; + +type YX = (usize, usize); + +fn parse(data: &str) -> Option<(Vec>, YX, &str)> { + let (map, moves) = data.split_once("\n\n").unwrap_or((data, "")); + let map = map + .lines() + .map(|line| line.bytes().collect()) + .collect::>>(); + let robot = map.iter().enumerate().find_map(|(y, line)| { + line.iter() + .enumerate() + .find(|(_, b)| **b == b'@') + .map(|(x, _)| (y, x)) + }); + Some((map, robot?, moves)) +} + +fn move_robot(map: &mut [Line], robot: YX, c: char) -> Option +where + Line: IndexMut, +{ + let (dy, dx) = match c { + '^' => (usize::MAX, 0), + 'v' => (1, 0), + '<' => (0, usize::MAX), + '>' => (0, 1), + _ => return None, + }; + let mut levels = vec![]; + let mut front: BTreeSet<_> = [robot].into(); + while !front.is_empty() { + let back = front; + front = BTreeSet::new(); + for (y, x) in &back { + let (y, x) = (y.wrapping_add(dy), x.wrapping_add(dx)); + match map[y][x] { + b'.' => continue, + b'O' => { + front.insert((y, x)); + } + b'[' => { + front.insert((y, x)); + if dy != 0 { + front.insert((y, x + 1)); + } + } + b']' => { + front.insert((y, x)); + if dy != 0 { + front.insert((y, x - 1)); + } + } + _ => return None, + } + } + levels.push(back); + } + for front in levels.into_iter().rev() { + for (y, x) in front { + map[y.wrapping_add(dy)][x.wrapping_add(dx)] = map[y][x]; + map[y][x] = b'.'; + } + } + Some((robot.0.wrapping_add(dy), robot.1.wrapping_add(dx))) +} + +pub fn part1(data: &str) -> Option { + let (mut map, mut robot, moves) = parse(data)?; + for c in moves.chars() { + robot = move_robot(&mut map, robot, c).unwrap_or(robot); + } + Some( + map.into_iter() + .enumerate() + .flat_map(|(y, line)| { + line.into_iter().enumerate().filter_map(move |(x, b)| { + if b == b'O' { + Some(100 * y + x) + } else { + None + } + }) + }) + .sum(), + ) +} + +pub fn part2(data: &str) -> Option { + let (mut map, mut robot, moves) = parse(data)?; + for line in &mut map { + *line = line + .iter() + .flat_map(|&b| match b { + b'@' => *b"@.", + b'O' => *b"[]", + _ => [b, b], + }) + .collect(); + } + robot.1 *= 2; + for c in moves.chars() { + robot = move_robot(&mut map, robot, c).unwrap_or(robot); + } + Some( + map.into_iter() + .enumerate() + .flat_map(|(y, line)| { + line.into_iter().enumerate().filter_map(move |(x, b)| { + if b == b'[' { + Some(100 * y + x) + } else { + None + } + }) + }) + .sum(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + static EXAMPLE_1: &str = indoc! {" + ########## + #..O..O.O# + #......O.# + #.OO..O.O# + #..O@..O.# + #O#..O...# + #O..O..O.# + #.OO.O.OO# + #....O...# + ########## + + ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ + vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< + <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ + ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< + ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ + <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> + ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< + v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ + "}; + + static EXAMPLE_2: &str = indoc! {" + ######## + #..O.O.# + ##@.O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + <^^>>>vv>v<< + "}; + + static EXAMPLE_3: &str = indoc! {" + ####### + #...#.# + #.....# + #..OO@# + #..O..# + #.....# + ####### + + anyhow::Result<()> { println!(); } + if args.is_empty() || args.contains("15") { + println!("Day 15"); + let data = get_day_input(15)?; + println!("{:?}", day15::part1(&data).ok_or(anyhow!("None"))?); + println!("{:?}", day15::part2(&data).ok_or(anyhow!("None"))?); + println!(); + } + Ok(()) }