Solve day 10
This commit is contained in:
parent
ec545d1b06
commit
ab7b9af735
8 changed files with 368 additions and 0 deletions
5
input/10-test-1
Normal file
5
input/10-test-1
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
-L|F7
|
||||||
|
7S-7|
|
||||||
|
L|7||
|
||||||
|
-L-J|
|
||||||
|
L|-JF
|
5
input/10-test-2
Normal file
5
input/10-test-2
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
7-F7-
|
||||||
|
.FJ|7
|
||||||
|
SJLL7
|
||||||
|
|F--J
|
||||||
|
LJ.LJ
|
9
input/10-test-3
Normal file
9
input/10-test-3
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
...........
|
||||||
|
.S-------7.
|
||||||
|
.|F-----7|.
|
||||||
|
.||.....||.
|
||||||
|
.||.....||.
|
||||||
|
.|L-7.F-J|.
|
||||||
|
.|..|.|..|.
|
||||||
|
.L--J.L--J.
|
||||||
|
...........
|
9
input/10-test-4
Normal file
9
input/10-test-4
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
..........
|
||||||
|
.S------7.
|
||||||
|
.|F----7|.
|
||||||
|
.||....||.
|
||||||
|
.||....||.
|
||||||
|
.|L-7F-J|.
|
||||||
|
.|..||..|.
|
||||||
|
.L--JL--J.
|
||||||
|
..........
|
10
input/10-test-5
Normal file
10
input/10-test-5
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.F----7F7F7F7F-7....
|
||||||
|
.|F--7||||||||FJ....
|
||||||
|
.||.FJ||||||||L7....
|
||||||
|
FJL7L7LJLJ||LJ.L-7..
|
||||||
|
L--J.L7...LJS7F-7L7.
|
||||||
|
....F-J..F7FJ|L7L7L7
|
||||||
|
....L7.F7||L7|.L7L7|
|
||||||
|
.....|FJLJ|FJ|F7|.LJ
|
||||||
|
....FJL-7.||.||||...
|
||||||
|
....L---J.LJ.LJLJ...
|
10
input/10-test-6
Normal file
10
input/10-test-6
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FF7FSF7F7F7F7F7F---7
|
||||||
|
L|LJ||||||||||||F--J
|
||||||
|
FL-7LJLJ||||||LJL-77
|
||||||
|
F--JF--7||LJLJ7F7FJ-
|
||||||
|
L---JF-JLJ.||-FJLJJ7
|
||||||
|
|F|F-JF---7F7-L7L|7|
|
||||||
|
|FFJF7L7F-JF7|JL---7
|
||||||
|
7-L-JL7||F7|L7F-7F7|
|
||||||
|
L.L7LFJ|||||FJL7||LJ
|
||||||
|
L7JLJL-JLJLJL--JLJ.L
|
267
src/bin/10.rs
Normal file
267
src/bin/10.rs
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
use aoc2023::{assert_example, Vec2};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const INPUT: &str = include_str!("../../input/10");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_example!(part1, "10-test-1", 4);
|
||||||
|
assert_example!(part1, "10-test-2", 8);
|
||||||
|
println!("Part 1: {}", part1(INPUT));
|
||||||
|
|
||||||
|
assert_example!(part2, "10-test-3", 4);
|
||||||
|
assert_example!(part2, "10-test-4", 4);
|
||||||
|
assert_example!(part2, "10-test-5", 8);
|
||||||
|
assert_example!(part2, "10-test-6", 10);
|
||||||
|
println!("Part 2: {}", part2(INPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part1(input: &str) -> usize {
|
||||||
|
parse(input).length() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part2(input: &str) -> usize {
|
||||||
|
parse(input).count_empty_tiles_inside()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Maze {
|
||||||
|
tiles: HashMap<Vec2<i64>, Tile>,
|
||||||
|
start: Vec2<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Maze {
|
||||||
|
/// Find all tile positions that form a closed loop.
|
||||||
|
fn pipe_loop(&self) -> HashMap<Vec2<i64>, PipeSegmentDirection> {
|
||||||
|
let mut pipes = HashMap::new();
|
||||||
|
let mut position = self.start;
|
||||||
|
let mut came_from = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let current_tile = *self.tiles.get(&position).unwrap();
|
||||||
|
|
||||||
|
match current_tile {
|
||||||
|
Tile::Empty => panic!("followed the maze to an empty tile?! {position:?}"),
|
||||||
|
Tile::Start if !pipes.is_empty() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Tile::Start => {
|
||||||
|
let (to, from) = self.start_connections();
|
||||||
|
let segment_direction = PipeSegmentDirection::new(from, to);
|
||||||
|
pipes.insert(position, segment_direction);
|
||||||
|
position += to.to_vec2();
|
||||||
|
came_from = Some(to.invert());
|
||||||
|
}
|
||||||
|
Tile::Pipe(pipe) => {
|
||||||
|
let from = came_from.expect("came_from");
|
||||||
|
let to = pipe.other_side(from).expect("other side");
|
||||||
|
let segment_direction = PipeSegmentDirection::new(from, to);
|
||||||
|
pipes.insert(position, segment_direction);
|
||||||
|
position += to.to_vec2();
|
||||||
|
came_from = Some(to.invert());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count the number of tiles inside the polygon described by the pipe loop
|
||||||
|
/// using the [winding number algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm).
|
||||||
|
fn count_empty_tiles_inside(&self) -> usize {
|
||||||
|
let max_dimensions = self.tiles.keys().fold(Vec2::<i64>::default(), |a, b| Vec2 {
|
||||||
|
x: a.x.max(b.x),
|
||||||
|
y: a.y.max(b.y),
|
||||||
|
});
|
||||||
|
let pipe_loop = self.pipe_loop();
|
||||||
|
|
||||||
|
let mut tiles_inside = 0;
|
||||||
|
|
||||||
|
for y in 0..=max_dimensions.y {
|
||||||
|
let mut winding = 0;
|
||||||
|
// Use this to prevent consecutive runs of changes in winding number.
|
||||||
|
let mut last_change = None;
|
||||||
|
|
||||||
|
for x in 0..=max_dimensions.x {
|
||||||
|
let position = Vec2::new(x, y);
|
||||||
|
let pipe_segment = pipe_loop.get(&position).copied();
|
||||||
|
|
||||||
|
if let Some(pipe_segment) = pipe_segment {
|
||||||
|
if Some(pipe_segment) == last_change {
|
||||||
|
// We already did this change
|
||||||
|
} else {
|
||||||
|
match pipe_segment.winding() {
|
||||||
|
None => {}
|
||||||
|
Some(w) => {
|
||||||
|
winding += w;
|
||||||
|
last_change = Some(pipe_segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
last_change = None;
|
||||||
|
if winding != 0 {
|
||||||
|
tiles_inside += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles_inside
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self) -> usize {
|
||||||
|
self.pipe_loop().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the pipes that are connected to the start.
|
||||||
|
fn start_connections(&self) -> (Direction, Direction) {
|
||||||
|
let neighbors = Direction::all().into_iter().flat_map(|dir| {
|
||||||
|
let pos = dir.to_vec2() + self.start;
|
||||||
|
self.tiles.get(&pos).map(|&tile| (dir, tile))
|
||||||
|
});
|
||||||
|
let connected_neighbors = neighbors.filter(|(dir, tile)| match tile {
|
||||||
|
Tile::Empty => false,
|
||||||
|
Tile::Start => panic!("move_from_start has start as neighbor?!"),
|
||||||
|
Tile::Pipe(pipe) => pipe.other_side(dir.invert()).is_some(),
|
||||||
|
});
|
||||||
|
let mut connected = connected_neighbors.map(|(dir, _tile)| dir);
|
||||||
|
|
||||||
|
let a = connected.next().expect("first start connection");
|
||||||
|
let b = connected.next().expect("second start connection");
|
||||||
|
assert_eq!(connected.next(), None, "Got a third start connection?!");
|
||||||
|
|
||||||
|
(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum PipeSegmentDirection {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipeSegmentDirection {
|
||||||
|
fn new(from: Direction, to: Direction) -> Self {
|
||||||
|
if from == Direction::North || to == Direction::South {
|
||||||
|
Self::Down
|
||||||
|
} else if from == Direction::South || to == Direction::North {
|
||||||
|
Self::Up
|
||||||
|
} else {
|
||||||
|
Self::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn winding(self) -> Option<i64> {
|
||||||
|
match self {
|
||||||
|
PipeSegmentDirection::Up => Some(1),
|
||||||
|
PipeSegmentDirection::Down => Some(-1),
|
||||||
|
PipeSegmentDirection::Other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Direction {
|
||||||
|
North,
|
||||||
|
East,
|
||||||
|
South,
|
||||||
|
West,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Direction {
|
||||||
|
fn to_vec2(self) -> Vec2<i64> {
|
||||||
|
match self {
|
||||||
|
Direction::North => Vec2::new(0, -1),
|
||||||
|
Direction::East => Vec2::new(1, 0),
|
||||||
|
Direction::South => Vec2::new(0, 1),
|
||||||
|
Direction::West => Vec2::new(-1, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all() -> [Self; 4] {
|
||||||
|
[
|
||||||
|
Direction::North,
|
||||||
|
Direction::East,
|
||||||
|
Direction::South,
|
||||||
|
Direction::West,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invert(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Direction::North => Direction::South,
|
||||||
|
Direction::East => Direction::West,
|
||||||
|
Direction::South => Direction::North,
|
||||||
|
Direction::West => Direction::East,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct Pipe {
|
||||||
|
a: Direction,
|
||||||
|
b: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipe {
|
||||||
|
/// Get the other side of the pipe given one side.
|
||||||
|
/// Returns None if there is no connection from the given side.
|
||||||
|
fn other_side(&self, from: Direction) -> Option<Direction> {
|
||||||
|
if self.a == from {
|
||||||
|
Some(self.b)
|
||||||
|
} else if self.b == from {
|
||||||
|
Some(self.a)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(a: Direction, b: Direction) -> Self {
|
||||||
|
Self { a, b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Tile {
|
||||||
|
Empty,
|
||||||
|
Start,
|
||||||
|
Pipe(Pipe),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for Tile {
|
||||||
|
fn from(value: char) -> Self {
|
||||||
|
match value {
|
||||||
|
'|' => Self::Pipe(Pipe::new(Direction::North, Direction::South)),
|
||||||
|
'-' => Self::Pipe(Pipe::new(Direction::East, Direction::West)),
|
||||||
|
'L' => Self::Pipe(Pipe::new(Direction::North, Direction::East)),
|
||||||
|
'J' => Self::Pipe(Pipe::new(Direction::North, Direction::West)),
|
||||||
|
'7' => Self::Pipe(Pipe::new(Direction::West, Direction::South)),
|
||||||
|
'F' => Self::Pipe(Pipe::new(Direction::South, Direction::East)),
|
||||||
|
'.' => Self::Empty,
|
||||||
|
'S' => Self::Start,
|
||||||
|
other => panic!("unknown tile '{other}'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(input: &str) -> Maze {
|
||||||
|
let mut tiles = HashMap::new();
|
||||||
|
let mut start = None;
|
||||||
|
|
||||||
|
for (y, line) in input.lines().enumerate() {
|
||||||
|
for (x, c) in line.chars().enumerate() {
|
||||||
|
let tile = c.into();
|
||||||
|
let position = Vec2::new(x as i64, y as i64);
|
||||||
|
|
||||||
|
if tile == Tile::Start {
|
||||||
|
start = Some(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.insert(position, tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = start.expect("start position");
|
||||||
|
|
||||||
|
Maze { tiles, start }
|
||||||
|
}
|
53
src/lib.rs
53
src/lib.rs
|
@ -1,5 +1,6 @@
|
||||||
//! This library contains useful helper functions that may be useful in several problems.
|
//! This library contains useful helper functions that may be useful in several problems.
|
||||||
|
|
||||||
|
use std::ops::{Add, AddAssign};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
ops::{Div, Mul, Rem},
|
ops::{Div, Mul, Rem},
|
||||||
|
@ -91,3 +92,55 @@ where
|
||||||
let gcd = gcd(a, b);
|
let gcd = gcd(a, b);
|
||||||
a / gcd * b
|
a / gcd * b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a function and a name of a file in the `input` directory,
|
||||||
|
/// assert that the function applied to the contents of the file returns the expected result.
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_example {
|
||||||
|
($solve:ident, $file:expr, $expected:expr) => {
|
||||||
|
assert_eq!(
|
||||||
|
$solve(include_str!(concat!("../../input/", $file))),
|
||||||
|
$expected,
|
||||||
|
"{}, {}",
|
||||||
|
stringify!($solve),
|
||||||
|
$file,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
pub struct Vec2<T> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Vec2<T> {
|
||||||
|
pub fn new(x: T, y: T) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Add for Vec2<T>
|
||||||
|
where
|
||||||
|
T: Add<Output = T>,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x + rhs.x,
|
||||||
|
y: self.y + rhs.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AddAssign for Vec2<T>
|
||||||
|
where
|
||||||
|
T: AddAssign,
|
||||||
|
{
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.x += rhs.x;
|
||||||
|
self.y += rhs.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue