Compare commits

...

4 Commits

Author SHA1 Message Date
Jan-Bulthuis 2b379bba1d day21 fast 2024-12-21 21:16:07 +01:00
Jan-Bulthuis e570358fa7 day21 working but not fast enough for part 2 2024-12-21 17:43:40 +01:00
Jan-Bulthuis 8e9dc7c388 day21 flawed 2024-12-21 13:03:49 +01:00
Jan-Bulthuis e857602932 day20 2024-12-20 06:33:15 +01:00
3 changed files with 561 additions and 3 deletions

256
aoc_2024/src/day20.rs Normal file
View File

@ -0,0 +1,256 @@
use std::usize;
use aoc_runner_derive::{aoc, aoc_generator};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cell {
Empty,
Wall,
}
type Input = ((usize, usize), (usize, usize), Vec<Vec<Cell>>);
#[aoc_generator(day20)]
fn parse(input: &str) -> Input {
let mut start = (0, 0);
let mut end = (0, 0);
let map = input
.lines()
.enumerate()
.map(|(y, line)| {
line.chars()
.enumerate()
.map(|(x, c)| match c {
'.' => Cell::Empty,
'#' => Cell::Wall,
'S' => {
start = (x, y);
Cell::Empty
}
'E' => {
end = (x, y);
Cell::Empty
}
_ => panic!("unexpected character: {}", c),
})
.collect()
})
.collect();
(start, end, map)
}
#[aoc(day20, part1)]
fn part1(input: &Input) -> usize {
let start = input.0;
let end = input.1;
let map = &input.2;
#[cfg(not(test))]
let min_saving = 100;
#[cfg(test)]
let min_saving = 2;
let w = map[0].len();
let h = map.len();
let mut dist = vec![vec![usize::MAX; w]; h];
dist[start.1][start.0] = 0;
let mut deque = std::collections::VecDeque::new();
deque.push_back((0, start));
while let Some((d, pos)) = deque.pop_front() {
if dist[pos.1][pos.0] < d {
continue;
}
dist[pos.1][pos.0] = d;
[(1, 0), (-1, 0), (0, -1), (0, 1)]
.iter()
.for_each(|(dx, dy)| {
let x = (pos.0 as isize + dx) as usize;
let y = (pos.1 as isize + dy) as usize;
if x < w && y < h && map[y][x] == Cell::Empty {
let new_d = d + 1;
if new_d < dist[y][x] {
deque.push_back((new_d, (x, y)));
}
}
})
}
map.iter()
.enumerate()
.map(|(y, line)| {
line.iter()
.enumerate()
.map(|(x, cell)| {
let y = y as isize;
if *cell == Cell::Empty {
[
(0, 2),
(0, -2),
(2, 0),
(-2, 0),
(1, 1),
(1, -1),
(-1, 1),
(-1, -1),
]
.iter()
.filter(|(dx, dy)| {
let nx = x as isize + dx;
let ny = y + dy;
nx >= 0
&& nx < w as isize
&& ny >= 0
&& ny < h as isize
&& map[ny as usize][nx as usize] == Cell::Empty
&& dist[ny as usize][nx as usize]
>= dist[y as usize][x] + min_saving + 2
})
.count()
} else {
0
}
})
.sum::<usize>()
})
.sum()
}
#[aoc(day20, part2)]
fn part2(input: &Input) -> usize {
let start = input.0;
let end = input.1;
let map = &input.2;
#[cfg(not(test))]
let min_saving = 100;
#[cfg(test)]
let min_saving = 60;
let max_skip = 20;
let w = map[0].len();
let h = map.len();
let mut dist = vec![vec![usize::MAX; w]; h];
dist[start.1][start.0] = 0;
let mut deque = std::collections::VecDeque::new();
deque.push_back((0, start));
while let Some((d, pos)) = deque.pop_front() {
if dist[pos.1][pos.0] < d {
continue;
}
dist[pos.1][pos.0] = d;
[(1, 0), (-1, 0), (0, -1), (0, 1)]
.iter()
.for_each(|(dx, dy)| {
let x = (pos.0 as isize + dx) as usize;
let y = (pos.1 as isize + dy) as usize;
if x < w && y < h && map[y][x] == Cell::Empty {
let new_d = d + 1;
if new_d < dist[y][x] {
deque.push_back((new_d, (x, y)));
}
}
})
}
map.iter()
.enumerate()
.map(|(y, line)| {
line.iter()
.enumerate()
.map(|(x, cell)| {
let y = y as isize;
if *cell == Cell::Empty {
(-max_skip..=max_skip)
.flat_map(|dx: isize| {
let skip = max_skip - dx.abs();
(-skip..=skip).map(move |dy| (dx, dy))
})
.filter(|(dx, dy)| {
let nx = x as isize + dx;
let ny = y + dy;
nx >= 0
&& nx < w as isize
&& ny >= 0
&& ny < h as isize
&& map[ny as usize][nx as usize] == Cell::Empty
&& dist[ny as usize][nx as usize]
>= dist[y as usize][x]
+ min_saving
+ dx.unsigned_abs()
+ dy.unsigned_abs()
})
.count()
} else {
0
}
})
.sum::<usize>()
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_example() {
assert_eq!(
part1(&parse(
"###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############"
)),
44
);
}
#[test]
fn part2_example() {
assert_eq!(
part2(&parse(
"###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############"
)),
129
);
}
}

300
aoc_2024/src/day21.rs Normal file
View File

@ -0,0 +1,300 @@
use std::iter::once;
use aoc_runner_derive::{aoc, aoc_generator};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Path<T> {
steps: usize,
path: [Option<T>; 6],
}
impl<T: Copy> Path<T> {
fn new() -> Self {
Self {
steps: 0,
path: [None, None, None, None, None, None],
}
}
fn push(&mut self, button: T) {
self.path[self.steps] = Some(button);
self.steps += 1;
}
fn append(&mut self, other: &Self) {
for i in 0..other.steps {
self.push(other.path[i].unwrap());
}
}
}
impl<T> IntoIterator for Path<T> {
type Item = T;
type IntoIter = std::iter::Flatten<std::array::IntoIter<Option<T>, 6>>;
fn into_iter(self) -> Self::IntoIter {
self.path.into_iter().flatten()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Button {
Num(NumButton),
Dir(DirButton),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DirButton {
Up,
Down,
Left,
Right,
A,
}
impl DirButton {
fn pos(&self) -> (usize, usize) {
match self {
DirButton::Up => (1, 0),
DirButton::Down => (1, 1),
DirButton::Left => (0, 1),
DirButton::Right => (2, 1),
DirButton::A => (2, 0),
}
}
fn index(&self) -> usize {
match self {
DirButton::Up => 0,
DirButton::Down => 1,
DirButton::Left => 2,
DirButton::Right => 3,
DirButton::A => 4,
}
}
fn to(&self, other: &DirButton) -> [Option<Path<DirButton>>; 2] {
let start = self.pos();
let end = other.pos();
let mut x = Path::new();
let mut y = Path::new();
if start.0 < end.0 {
(start.0..end.0).for_each(|_| x.push(DirButton::Right));
} else {
(end.0..start.0).for_each(|_| x.push(DirButton::Left));
};
if start.1 < end.1 {
(start.1..end.1).for_each(|_| y.push(DirButton::Down));
} else {
(end.1..start.1).for_each(|_| y.push(DirButton::Up));
};
if start.0 == 0 && end.1 == 0 {
x.append(&y);
x.push(DirButton::A);
[Some(x), None]
} else if end.0 == 0 && start.1 == 0 {
y.append(&x);
y.push(DirButton::A);
[Some(y), None]
} else {
let x_copy = x;
x.append(&y);
x.push(DirButton::A);
y.append(&x_copy);
y.push(DirButton::A);
[Some(x), Some(y)]
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumButton {
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Zero,
A,
}
impl NumButton {
fn pos(&self) -> (usize, usize) {
match self {
NumButton::One => (0, 2),
NumButton::Two => (1, 2),
NumButton::Three => (2, 2),
NumButton::Four => (0, 1),
NumButton::Five => (1, 1),
NumButton::Six => (2, 1),
NumButton::Seven => (0, 0),
NumButton::Eight => (1, 0),
NumButton::Nine => (2, 0),
NumButton::Zero => (1, 3),
NumButton::A => (2, 3),
}
}
fn to(&self, other: &NumButton) -> [Option<Path<DirButton>>; 2] {
let start = self.pos();
let end = other.pos();
let mut x = Path::new();
let mut y = Path::new();
if start.0 < end.0 {
(start.0..end.0).for_each(|_| x.push(DirButton::Right));
} else {
(end.0..start.0).for_each(|_| x.push(DirButton::Left));
};
if start.1 < end.1 {
(start.1..end.1).for_each(|_| y.push(DirButton::Down));
} else {
(end.1..start.1).for_each(|_| y.push(DirButton::Up));
};
if start.0 == 0 && end.1 == 3 {
x.append(&y);
x.push(DirButton::A);
[Some(x), None]
} else if end.0 == 0 && start.1 == 3 {
y.append(&x);
y.push(DirButton::A);
[Some(y), None]
} else {
let x_copy = x;
x.append(&y);
x.push(DirButton::A);
y.append(&x_copy);
y.push(DirButton::A);
[Some(x), Some(y)]
}
}
}
type Input = Vec<(usize, Vec<NumButton>)>;
#[aoc_generator(day21)]
fn parse(input: &str) -> Input {
input
.lines()
.map(|line| {
(
line[0..3].parse().unwrap(),
line.chars()
.map(|c| match c {
'1' => NumButton::One,
'2' => NumButton::Two,
'3' => NumButton::Three,
'4' => NumButton::Four,
'5' => NumButton::Five,
'6' => NumButton::Six,
'7' => NumButton::Seven,
'8' => NumButton::Eight,
'9' => NumButton::Nine,
'0' => NumButton::Zero,
'A' => NumButton::A,
_ => unreachable!(),
})
.collect(),
)
})
.collect()
}
type DistanceMatrix = [[usize; 5]; 5];
fn calculate_steps(start: DirButton, end: DirButton, steps: &mut [DistanceMatrix]) -> usize {
if steps.is_empty() {
return 1;
}
if steps[0][start.index()][end.index()] == 0 {
let extension = start
.to(&end)
.into_iter()
.flatten()
.map(|path| shortest_dir_path(path, &mut steps[1..]))
.min()
.unwrap();
steps[0][start.index()][end.index()] = extension;
}
steps[0][start.index()][end.index()]
}
fn shortest_dir_path(path: Path<DirButton>, steps: &mut [DistanceMatrix]) -> usize {
path.path
.into_iter()
.flatten()
.fold((0, DirButton::A), |acc, next| {
let sum = acc.0;
let pos = acc.1;
let extension = calculate_steps(pos, next, steps);
(sum + extension, next)
})
.0
}
fn shortest_path(path: Vec<NumButton>, steps: &mut [DistanceMatrix]) -> usize {
path.into_iter()
.fold((0, NumButton::A), |acc, end| {
let sum = acc.0;
let start = acc.1;
let extension = start
.to(&end)
.into_iter()
.flatten()
.map(|path| shortest_dir_path(path, steps))
.min()
.unwrap();
(sum + extension, end)
})
.0
}
#[aoc(day21, part1)]
fn part1(input: &Input) -> usize {
let mut steps = vec![[[0; 5]; 5]; 3 - 1];
input
.iter()
.cloned()
.map(|(num, path)| num * shortest_path(path, &mut steps))
.sum()
}
#[aoc(day21, part2)]
fn part2(input: &Input) -> usize {
let mut steps = vec![[[0; 5]; 5]; 26 - 1];
input
.iter()
.cloned()
.map(|(num, path)| num * shortest_path(path, &mut steps))
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_example() {
assert_eq!(part1(&parse("179A")), 68 * 179);
assert_eq!(part1(&parse("379A")), 64 * 379);
assert_eq!(part1(&parse("456A")), 64 * 456);
assert_eq!(part1(&parse("980A")), 60 * 980);
assert_eq!(part1(&parse("029A")), 68 * 29);
}
}

View File

@ -1,6 +1,4 @@
mod day19; mod day21;
mod day18;
mod day17;
mod day1; mod day1;
mod day10; mod day10;
mod day11; mod day11;
@ -9,7 +7,11 @@ mod day13;
mod day14; mod day14;
mod day15; mod day15;
mod day16; mod day16;
mod day17;
mod day18;
mod day19;
mod day2; mod day2;
mod day20;
mod day3; mod day3;
mod day4; mod day4;
mod day5; mod day5;