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 = "day13-maze_of_twisty_little_cubicles"
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,61 @@
\--- Day 13: A Maze of Twisty Little Cubicles ---
----------
You arrive at the first floor of this new building to discover a much less welcoming environment than the shiny atrium of the last one. Instead, you are in a maze of twisty little cubicles, all alike.
Every location in this area is addressed by a pair of non-negative integers (`x,y`). Each such coordinate is either a wall or an open space. You can't move diagonally. The cube maze starts at `0,0` and seems to extend infinitely toward *positive* `x` and `y`; negative values are *invalid*, as they represent a location outside the building. You are in a small waiting area at `1,1`.
While it seems chaotic, a nearby morale-boosting poster explains, the layout is actually quite logical. You can determine whether a given `x,y` coordinate will be a wall or an open space using a simple system:
* Find `x*x + 3*x + 2*x*y + y + y*y`.
* Add the office designer's favorite number (your puzzle input).
* Find the [binary representation](https://en.wikipedia.org/wiki/Binary_number) of that sum; count the *number* of [bits](https://en.wikipedia.org/wiki/Bit) that are `1`.
* If the number of bits that are `1` is *even*, it's an *open space*.
* If the number of bits that are `1` is *odd*, it's a *wall*.
For example, if the office designer's favorite number were `10`, drawing walls as `#` and open spaces as `.`, the corner of the building containing `0,0` would look like this:
```
0123456789
0 .#.####.##
1 ..#..#...#
2 #....##...
3 ###.#.###.
4 .##..#..#.
5 ..##....#.
6 #...##.###
```
Now, suppose you wanted to reach `7,4`. The shortest route you could take is marked as `O`:
```
0123456789
0 .#.####.##
1 .O#..#...#
2 #OOO.##...
3 ###O#.###.
4 .##OO#OO#.
5 ..##OOO.#.
6 #...##.###
```
Thus, reaching `7,4` would take a minimum of `11` steps (starting from your current location, `1,1`).
What is the *fewest number of steps required* for you to reach `31,39`?
Your puzzle answer was `90`.
\--- Part Two ---
----------
*How many locations* (distinct `x,y` coordinates, including your starting location) can you reach in at most `50` steps?
Your puzzle answer was `135`.
Both parts of this puzzle are complete! They provide two gold stars: \*\*
At this point, all that is left is for you to [admire your Advent calendar](/2016).
Your puzzle input was `1352`.

View file

@ -0,0 +1,76 @@
use std::collections::{HashMap, HashSet};
pub fn run(input: usize, start: (usize, usize), goal: (usize, usize)) -> (usize, usize) {
let first = count_steps(start, goal, input);
let second = get_all(HashSet::from([start]), HashSet::from([start]), input, 50).len();
(first, second)
}
fn get_all(current: HashSet<(usize, usize)>, new_last_step: HashSet<(usize, usize)>, input: usize, steps_left: usize) -> HashSet<(usize, usize)> {
if steps_left == 0 {
return current;
}
let new_this_step: HashSet<(usize, usize)> = new_last_step.iter().flat_map(|coord| get_neighbours(*coord, input).into_iter().filter(|neighbour| !current.contains(neighbour))).collect();
get_all(current.union(&new_this_step).cloned().collect(), new_this_step, input, steps_left-1)
}
fn is_open_space((x, y): (usize, usize), input: usize) -> bool {
(x*x + 3*x + 2*x*y + y + y*y + input).count_ones() % 2 == 0
}
fn get_neighbours(current: (usize, usize), input: usize) -> Vec<(usize, usize)> {
let mut res = vec![(current.0, current.1 + 1), (current.0 + 1, current.1)];
if current.0 > 0 {
res.push((current.0-1, current.1));
}
if current.1 > 0 {
res.push((current.0, current.1-1));
}
res.into_iter().filter(|&coords| is_open_space(coords, input)).collect()
}
// A* search
fn count_steps(start: (usize, usize), goal: (usize, usize), input: usize) -> usize {
let mut open_set = HashSet::from([start]);
let mut g_scores = HashMap::from([(start, 0)]);
let mut f_scores = HashMap::from([(start, start.0.abs_diff(goal.0)+start.1.abs_diff(goal.1))]);
while !open_set.is_empty() {
let current = *open_set.iter().min_by_key(|c| f_scores.get(c).unwrap()).unwrap();
open_set.remove(&current);
let current_g = *g_scores.get(&current).unwrap();
if current == goal {
return current_g;
}
for neighbour in get_neighbours(current, input) {
let tentative_g_score = current_g + 1;
if g_scores.get(&neighbour).unwrap_or(&usize::MAX) > &tentative_g_score {
g_scores.insert(neighbour, tentative_g_score);
open_set.insert(neighbour);
f_scores.insert(neighbour, tentative_g_score + neighbour.0.abs_diff(goal.0) + neighbour.1.abs_diff(goal.1));
}
}
}
panic!("No solution found");
}
#[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)[..])
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input").trim().parse().unwrap();
assert_eq!(run(sample_input, (1, 1), (7, 4)), (11, 151));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input").trim().parse().unwrap();
assert_eq!(run(challenge_input, (1, 1), (31, 39)), (90, 135));
}
}

View file

@ -0,0 +1 @@
1352

View file

@ -0,0 +1 @@
10