Add 2024 Day 18
This commit is contained in:
parent
bb8fc8c5f0
commit
6d51162683
5 changed files with 3657 additions and 0 deletions
13
2024/day18_ram_run/Cargo.toml
Normal file
13
2024/day18_ram_run/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "day18_ram_run"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "test_benchmark"
|
||||
harness = false
|
74
2024/day18_ram_run/challenge.md
Normal file
74
2024/day18_ram_run/challenge.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
You and The Historians look a lot more pixelated than you remember. You're [inside a computer](/2017/day/2) at the North Pole!
|
||||
|
||||
Just as you're about to check out your surroundings, a program runs up to you. "This region of memory isn't safe! The User misunderstood what a [pushdown automaton](https://en.wikipedia.org/wiki/Pushdown_automaton) is and their algorithm is pushing whole *bytes* down on top of us! Run!"
|
||||
|
||||
The algorithm is fast - it's going to cause a byte to fall into your memory space once every [nanosecond](https://www.youtube.com/watch?v=9eyFDBPk4Yw)! Fortunately, you're *faster*, and by quickly scanning the algorithm, you create a *list of which bytes will fall* (your puzzle input) in the order they'll land in your memory space.
|
||||
|
||||
Your memory space is a two-dimensional grid with coordinates that range from `0` to `70` both horizontally and vertically. However, for the sake of example, suppose you're on a smaller grid with coordinates that range from `0` to `6` and the following list of incoming byte positions:
|
||||
|
||||
```
|
||||
5,4
|
||||
4,2
|
||||
4,5
|
||||
3,0
|
||||
2,1
|
||||
6,3
|
||||
2,4
|
||||
1,5
|
||||
0,6
|
||||
3,3
|
||||
2,6
|
||||
5,1
|
||||
1,2
|
||||
5,5
|
||||
2,5
|
||||
6,5
|
||||
1,4
|
||||
0,4
|
||||
6,4
|
||||
1,1
|
||||
6,1
|
||||
1,0
|
||||
0,5
|
||||
1,6
|
||||
2,0
|
||||
|
||||
```
|
||||
|
||||
Each byte position is given as an `X,Y` coordinate, where `X` is the distance from the left edge of your memory space and `Y` is the distance from the top edge of your memory space.
|
||||
|
||||
You and The Historians are currently in the top left corner of the memory space (at `0,0`) and need to reach the exit in the bottom right corner (at `70,70` in your memory space, but at `6,6` in this example). You'll need to simulate the falling bytes to plan out where it will be safe to run; for now, simulate just the first few bytes falling into your memory space.
|
||||
|
||||
As bytes fall into your memory space, they make that coordinate *corrupted*. Corrupted memory coordinates cannot be entered by you or The Historians, so you'll need to plan your route carefully. You also cannot leave the boundaries of the memory space; your only hope is to reach the exit.
|
||||
|
||||
In the above example, if you were to draw the memory space after the first `12` bytes have fallen (using `.` for safe and `#` for corrupted), it would look like this:
|
||||
|
||||
```
|
||||
...#...
|
||||
..#..#.
|
||||
....#..
|
||||
...#..#
|
||||
..#..#.
|
||||
.#..#..
|
||||
#.#....
|
||||
|
||||
```
|
||||
|
||||
You can take steps up, down, left, or right. After just 12 bytes have corrupted locations in your memory space, the shortest path from the top left corner to the exit would take `*22*` steps. Here (marked with `O`) is one such path:
|
||||
|
||||
```
|
||||
OO.#OOO
|
||||
.O#OO#O
|
||||
.OOO#OO
|
||||
...#OO#
|
||||
..#OO#.
|
||||
.#.O#..
|
||||
#.#OOOO
|
||||
|
||||
```
|
||||
|
||||
Simulate the first kilobyte (`1024` bytes) falling onto your memory space. Afterward, *what is the minimum number of steps needed to reach the exit?*
|
||||
|
||||
To begin, [get your puzzle input](18/input).
|
||||
|
||||
Answer:
|
95
2024/day18_ram_run/src/lib.rs
Normal file
95
2024/day18_ram_run/src/lib.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use core::fmt::Display;
|
||||
use std::{collections::{HashSet, VecDeque}, num::ParseIntError};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError<'a> {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
LineMalformed(&'a str),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError<'_> {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Coordinates = (i8, i8);
|
||||
|
||||
fn try_parse_pair(value: &str) -> Result<Coordinates, ParseError> {
|
||||
if let Some ((lhs, rhs)) = value.split_once(',') {
|
||||
Ok((lhs.parse()?, rhs.parse()?))
|
||||
} else {
|
||||
Err(ParseError::LineMalformed(value))
|
||||
}
|
||||
}
|
||||
|
||||
fn find_path(blocked: &HashSet<Coordinates>, destination: Coordinates) -> usize {
|
||||
let mut open_set = VecDeque::from([((0, 0), 0)]);
|
||||
let mut visited = HashSet::new();
|
||||
while let Some(((x, y), steps)) = open_set.pop_front() {
|
||||
if (x, y) == destination {
|
||||
return steps;
|
||||
}
|
||||
[(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
|
||||
.into_iter()
|
||||
.filter(|&(x, y)| !blocked.contains(&(x, y)) &&
|
||||
x >= 0 && y >= 0 && x <= destination.0 && y <= destination.1 )
|
||||
.for_each(|new_pos| if !visited.contains(&new_pos) {
|
||||
visited.insert(new_pos);
|
||||
open_set.push_back((new_pos, steps+1));
|
||||
});
|
||||
}
|
||||
usize::MAX
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(usize, String), ParseError> {
|
||||
run_challenge(input, (70, 70), 1024)
|
||||
}
|
||||
|
||||
pub fn run_sample(input: &str) -> Result<(usize, String), ParseError> {
|
||||
run_challenge(input, (6, 6), 12)
|
||||
}
|
||||
|
||||
fn run_challenge(input: &str, destination: Coordinates, simulate_bytes: usize) -> Result<(usize, String), ParseError> {
|
||||
let blocked: HashSet<_> = input.lines().take(simulate_bytes).map(try_parse_pair).collect::<Result<HashSet<_>, _>>()?;
|
||||
let first = find_path(&blocked, destination);
|
||||
let rest: Vec<_> = input.lines().skip(simulate_bytes).map(try_parse_pair).collect::<Result<Vec<_>, _>>()?;
|
||||
let step = (0..rest.len()).collect::<Vec<_>>().partition_point(|&steps| {
|
||||
let mut blocked = blocked.clone();
|
||||
rest[..=steps].iter().for_each(|byte| _ = blocked.insert(*byte));
|
||||
find_path(&blocked, destination) < usize::MAX
|
||||
});
|
||||
let second = format!("{},{}", rest[step].0, rest[step].1);
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run_sample(&sample_input), Ok((22, "6,1".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok((302, "24,32".to_string())));
|
||||
}
|
||||
}
|
3450
2024/day18_ram_run/tests/challenge_input
Normal file
3450
2024/day18_ram_run/tests/challenge_input
Normal file
File diff suppressed because it is too large
Load diff
25
2024/day18_ram_run/tests/sample_input
Normal file
25
2024/day18_ram_run/tests/sample_input
Normal file
|
@ -0,0 +1,25 @@
|
|||
5,4
|
||||
4,2
|
||||
4,5
|
||||
3,0
|
||||
2,1
|
||||
6,3
|
||||
2,4
|
||||
1,5
|
||||
0,6
|
||||
3,3
|
||||
2,6
|
||||
5,1
|
||||
1,2
|
||||
5,5
|
||||
2,5
|
||||
6,5
|
||||
1,4
|
||||
0,4
|
||||
6,4
|
||||
1,1
|
||||
6,1
|
||||
1,0
|
||||
0,5
|
||||
1,6
|
||||
2,0
|
Loading…
Add table
Add a link
Reference in a new issue