Solutions for 2022, as well as 2015-2018 and 2019 up to day 11

This commit is contained in:
Chris Alge 2023-03-12 15:20:02 +01:00
commit 1895197c49
722 changed files with 375457 additions and 0 deletions

View file

@ -0,0 +1,8 @@
[package]
name = "day22_mode_maze"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,351 @@
This is it, your final stop: the year -483. It's snowing and dark outside; the only light you can see is coming from a small cottage in the distance. You make your way there and knock on the door.
A portly man with a large, white beard answers the door and invites you inside. For someone living near the North Pole in -483, he must not get many visitors, but he doesn't act surprised to see you. Instead, he offers you some milk and cookies.
After talking for a while, he asks a favor of you. His friend hasn't come back in a few hours, and he's not sure where he is. Scanning the region briefly, you discover one life signal in a cave system nearby; his friend must have taken shelter there. The man asks if you can go there to retrieve his friend.
The cave is divided into square *regions* which are either dominantly *rocky*, *narrow*, or *wet* (called its *type*). Each region occupies exactly one *coordinate* in `X,Y` format where `X` and `Y` are integers and zero or greater. (Adjacent regions can be the same type.)
The scan (your puzzle input) is not very detailed: it only reveals the *depth* of the cave system and the *coordinates of the target*. However, it does not reveal the type of each region. The mouth of the cave is at `0,0`.
The man explains that due to the unusual geology in the area, there is a method to determine any region's type based on its *erosion level*. The erosion level of a region can be determined from its *geologic index*. The geologic index can be determined using the first rule that applies from the list below:
* The region at `0,0` (the mouth of the cave) has a geologic index of `0`.
* The region at the coordinates of the target has a geologic index of `0`.
* If the region's `Y` coordinate is `0`, the geologic index is its `X` coordinate times `16807`.
* If the region's `X` coordinate is `0`, the geologic index is its `Y` coordinate times `48271`.
* Otherwise, the region's geologic index is the result of multiplying the erosion *levels* of the regions at `X-1,Y` and `X,Y-1`.
A region's *erosion level* is its *geologic index* plus the cave system's *depth*, all [modulo](https://en.wikipedia.org/wiki/Modulo_operation) `20183`. Then:
* If the *erosion level modulo `3`* is `0`, the region's type is *rocky*.
* If the *erosion level modulo `3`* is `1`, the region's type is *wet*.
* If the *erosion level modulo `3`* is `2`, the region's type is *narrow*.
For example, suppose the cave system's depth is `510` and the target's coordinates are `10,10`. Using `%` to represent the modulo operator, the cavern would look as follows:
* At `0,0`, the geologic index is `0`. The erosion level is `(0 + 510) % 20183 = 510`. The type is `510 % 3 = 0`, *rocky*.
* At `1,0`, because the `Y` coordinate is `0`, the geologic index is `1 * 16807 = 16807`. The erosion level is `(16807 + 510) % 20183 = 17317`. The type is `17317 % 3 = 1`, *wet*.
* At `0,1`, because the `X` coordinate is `0`, the geologic index is ` 1 * 48271 = 48271`. The erosion level is `(48271 + 510) % 20183 = 8415`. The type is `8415 % 3 = 0`, *rocky*.
* At `1,1`, neither coordinate is `0` and it is not the coordinate of the target, so the geologic index is the erosion level of `0,1` (`8415`) times the erosion level of `1,0` (`17317`), `8415 * 17317 = 145722555`. The erosion level is `(145722555 + 510) % 20183 = 1805`. The type is `1805 % 3 = 2`, *narrow*.
* At `10,10`, because they are the target's coordinates, the geologic index is `0`. The erosion level is `(0 + 510) % 20183 = 510`. The type is `510 % 3 = 0`, *rocky*.
Drawing this same cave system with rocky as `.`, wet as `=`, narrow as `|`, the mouth as `M`, the target as `T`, with `0,0` in the top-left corner, `X` increasing to the right, and `Y` increasing downward, the top-left corner of the map looks like this:
```
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
```
Before you go in, you should determine the *risk level* of the area. For the rectangle that has a top-left corner of region `0,0` and a bottom-right corner of the region containing the target, add up the risk level of each individual region: `0` for rocky regions, `1` for wet regions, and `2` for narrow regions.
In the cave system above, because the mouth is at `0,0` and the target is at `10,10`, adding up the risk level of all regions with an `X` coordinate from `0` to `10` and a `Y` coordinate from `0` to `10`, this total is `*114*`.
*What is the total risk level for the smallest rectangle that includes `0,0` and the target's coordinates?*
Your puzzle answer was `11810`.
\--- Part Two ---
----------
Okay, it's time to go rescue the man's friend.
As you leave, he hands you some tools: a *torch* and some *climbing gear*. You can't equip both tools at once, but you can choose to use *neither*.
Tools can only be used in certain regions:
* In *rocky* regions, you can use the *climbing gear* or the *torch*. You cannot use *neither* (you'll likely slip and fall).
* In *wet* regions, you can use the *climbing gear* or *neither* tool. You cannot use the *torch* (if it gets wet, you won't have a light source).
* In *narrow* regions, you can use the *torch* or *neither* tool. You cannot use the *climbing gear* (it's too bulky to fit).
You start at `0,0` (the mouth of the cave) with *the torch equipped* and must reach the target coordinates as quickly as possible. The regions with negative `X` or `Y` are solid rock and cannot be traversed. The fastest route might involve entering regions beyond the `X` or `Y` coordinate of the target.
You can *move to an adjacent region* (up, down, left, or right; never diagonally) if your currently equipped tool allows you to enter that region. Moving to an adjacent region takes *one minute*. (For example, if you have the *torch* equipped, you can move between *rocky* and *narrow* regions, but cannot enter *wet* regions.)
You can *change your currently equipped tool or put both away* if your new equipment would be valid for your current region. Switching to using the *climbing gear*, *torch*, or *neither* always takes *seven minutes*, regardless of which tools you start with. (For example, if you are in a *rocky* region, you can switch from the *torch* to the *climbing gear*, but you cannot switch to *neither*.)
Finally, once you reach the target, you need the *torch* equipped before you can find him in the dark. The target is always in a *rocky* region, so if you arrive there with *climbing gear* equipped, you will need to spend seven minutes switching to your torch.
For example, using the same cave system as above, starting in the top left corner (`0,0`) and moving to the bottom right corner (the target, `10,10`) as quickly as possible, one possible route is as follows, with your current position marked `X`:
```
Initially:
X=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Down:
M=.|=.|.|=.|=|=.
X|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Right:
M=.|=.|.|=.|=|=.
.X=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Switch from using the torch to neither tool:
M=.|=.|.|=.|=|=.
.X=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Right 3:
M=.|=.|.|=.|=|=.
.|=|X|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Switch from using neither tool to the climbing gear:
M=.|=.|.|=.|=|=.
.|=|X|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Down 7:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..X==..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Right:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..=X=..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Down 3:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||.X.|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Right:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||..X|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Down:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.X..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Right 4:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===T===||
=|||...|==..|=.|
=.=|=.=..=X||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Up 2:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===X===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
Switch from using the climbing gear to the torch:
M=.|=.|.|=.|=|=.
.|=|=|||..|.=...
.==|....||=..|==
=.|....|.==.|==.
=|..==...=.|==..
=||.=.=||=|=..|=
|.=.===|||..=..|
|..==||=.|==|===
.=..===..=|.|||.
.======|||=|=.|=
.===|=|===X===||
=|||...|==..|=.|
=.=|=.=..=.||==|
||=|=...|==.=|==
|=.=||===.|||===
||.|==.|.|.||=||
```
This is tied with other routes as the *fastest way to reach the target*: `*45*` minutes. In it, `21` minutes are spent switching tools (three times, seven minutes each) and the remaining `24` minutes are spent moving.
*What is the fewest number of minutes you can take to reach the target?*
Your puzzle answer was `1015`.
Both parts of this puzzle are complete! They provide two gold stars: \*\*
At this point, you should [return to your Advent calendar](/2018) and try another puzzle.
If you still want to see it, you can [get your puzzle input](22/input).

View file

@ -0,0 +1,157 @@
use std::collections::{HashMap, HashSet, BTreeSet};
struct Regions {
erosion_levels: HashMap<(usize, usize), usize>,
target: (usize, usize),
depth: usize,
}
impl Regions {
fn get_geo_index(&mut self, (x, y): (usize, usize)) -> usize {
if [(0, 0), self.target].contains(&(x, y)) {
0
} else if y == 0 {
x * 16807
} else if x == 0 {
y * 48271
} else {
self.get_erosion_level((x-1, y)) * self.get_erosion_level((x, y-1))
}
}
fn get_erosion_level(&mut self, coordinates: (usize, usize)) -> usize {
if let Some(res) = self.erosion_levels.get(&coordinates) {
*res
} else {
let res = (self.get_geo_index(coordinates) + self.depth) % 20183;
self.erosion_levels.insert(coordinates, res);
res
}
}
fn get_type_score(&mut self, coordinates: (usize, usize)) -> usize {
self.get_erosion_level(coordinates)%3
}
fn get_neighbours(&mut self, current: NavigationState) -> Vec<NavigationState> {
let (x, y) = current.coordinates;
// Determine the other tool allowed in this terrain:
// Their numbers all add up to 3 (0+1+2). Substract the one we already have equiped (we
// make sure this is allowed below) and the one not allowed here (which equals the terrain
// score) and we have left the allowed one.
let other_tool = 3-current.tool-self.get_type_score(current.coordinates);
// We can always stay in place and switch tools.
let mut res = Vec::from([NavigationState{
coordinates: (current.coordinates),
tool: other_tool,
time_elapsed: current.time_elapsed+7,
projected_total: current.time_elapsed+7 + x.abs_diff(self.target.0) + y.abs_diff(self.target.1) + 7*other_tool.abs_diff(1),
}]);
// For all directions (barring negative x and y): If our current tool is allowed there, we
// can go there in 1 minute.
if x>0 && self.get_type_score((x-1, y)) != current.tool {
res.push(NavigationState { coordinates: (x-1, y), tool: current.tool, time_elapsed: current.time_elapsed+1, projected_total: current.time_elapsed+1 + (x-1).abs_diff(self.target.0) + y.abs_diff(self.target.1) + 7*(current.tool.abs_diff(1)) });
}
if self.get_type_score((x+1, y)) != current.tool {
res.push(NavigationState { coordinates: (x+1, y), tool: current.tool, time_elapsed: current.time_elapsed+1, projected_total: current.time_elapsed+1 + (x+1).abs_diff(self.target.0) + y.abs_diff(self.target.1) + 7*(current.tool.abs_diff(1)) });
}
if y>0 && self.get_type_score((x, y-1)) != current.tool {
res.push(NavigationState { coordinates: (x, y-1), tool: current.tool, time_elapsed: current.time_elapsed+1, projected_total: current.time_elapsed+1 + x.abs_diff(self.target.0) + (y-1).abs_diff(self.target.1) + 7*(current.tool.abs_diff(1)) });
}
if self.get_type_score((x, y+1)) != current.tool {
res.push(NavigationState { coordinates: (x, y+1), tool: current.tool, time_elapsed: current.time_elapsed+1, projected_total: current.time_elapsed+1 + x.abs_diff(self.target.0) + (y+1).abs_diff(self.target.1) + 7*(current.tool.abs_diff(1)) });
}
res
}
}
#[derive(Debug, PartialEq, Eq)]
struct NavigationState {
coordinates: (usize, usize),
// the tool number equals the type score of the terrain where they aren't allowed, so we can
// reuse that for determining the allowed tools
tool: usize,
time_elapsed: usize,
// projected_total is the time it took to get here + the manhattan distance between current and target + 7 if the current tool
// is not 1 (Torch). This is guaranteed to be lower than or equal to the actual time, since we
// will never find a faster way here by visiting later and the other summands are the actual
// time remaining in the best case scenario.
projected_total: usize,
}
impl Ord for NavigationState {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.projected_total.cmp(&self.projected_total).then_with(|| other.coordinates.0.cmp(&self.coordinates.0)).then_with(|| other.coordinates.1.cmp(&self.coordinates.1)).then_with(|| other.tool.cmp(&self.tool))
}
}
impl PartialOrd for NavigationState {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub fn run(input: &str) -> (usize, usize) {
let lines: Vec<_> = input.lines().collect();
let depth = lines[0].split_whitespace().nth(1).unwrap().parse().unwrap();
let target_components: Vec<_> = lines[1].split_whitespace().nth(1).unwrap().split(',').collect();
let target = (target_components[0].parse().unwrap(), target_components[1].parse().unwrap());
let mut regions = Regions {
target,
depth,
erosion_levels: HashMap::new(),
};
let first = (0..=target.0).map(|x| (0..=target.1).map(|y| regions.get_type_score((x, y))).sum::<usize>()).sum();
let second = shortest_path(&mut regions);
(first, second)
}
fn shortest_path(regions: &mut Regions) -> usize {
let starting = NavigationState {
coordinates: (0, 0),
tool: 1,
time_elapsed: 0,
projected_total: regions.target.0 + regions.target.1,
};
let mut visited = HashSet::new();
let mut open = BTreeSet::from([starting]);
loop {
let current = open.pop_last().unwrap();
if current.coordinates == regions.target && current.tool == 1 {
return current.time_elapsed;
}
if visited.contains(&(current.coordinates, current.tool)) {
continue;
}
visited.insert((current.coordinates, current.tool));
regions.get_neighbours(current).into_iter().for_each(|neighbour| {
open.insert(neighbour);
});
}
}
#[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_input), (114, 45));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), (11810, 1015));
}
}

View file

@ -0,0 +1,2 @@
depth: 3558
target: 15,740

View file

@ -0,0 +1,2 @@
depth: 510
target: 10,10