day 10
This commit is contained in:
parent
9133cd21a5
commit
61690cda6c
|
@ -118,20 +118,6 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"pathfinding",
|
|
||||||
"rayon",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deprecate-until"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"semver",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -140,49 +126,12 @@ version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fixedbitset"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.14.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indoc"
|
name = "indoc"
|
||||||
version = "2.0.4"
|
version = "2.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "integer-sqrt"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
@ -299,39 +248,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pathfinding"
|
|
||||||
version = "4.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "752866d4511516a35883728309499db42696f388263586b70659a5461e641db5"
|
|
||||||
dependencies = [
|
|
||||||
"deprecate-until",
|
|
||||||
"fixedbitset",
|
|
||||||
"indexmap",
|
|
||||||
"integer-sqrt",
|
|
||||||
"num-traits",
|
|
||||||
"rustc-hash",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.70"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ranges"
|
name = "ranges"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -358,57 +274,8 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-hash"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.39"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.50"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.50"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
pathfinding = "4.4.0"
|
|
||||||
rayon.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{fmt::Debug, sync::Mutex};
|
use std::iter;
|
||||||
|
|
||||||
use pathfinding::prelude::bfs;
|
use itertools::Itertools;
|
||||||
use rayon::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("{}", part1(include_str!("./input.txt")));
|
println!("{}", part1(include_str!("./input.txt")));
|
||||||
|
@ -12,8 +11,8 @@ fn part1(input: &str) -> usize {
|
||||||
.lines()
|
.lines()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(y, line)| {
|
.map(|(y, line)| {
|
||||||
line.par_char_indices()
|
line.char_indices()
|
||||||
.map(|(x, c)| VPipe::new(x, y, c.try_into().expect("invalid pipe character")))
|
.map(|(x, c)| Pipe::new(x, y, c.try_into().expect("invalid pipe character")))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.collect::<Vec<Vec<_>>>();
|
.collect::<Vec<Vec<_>>>();
|
||||||
|
@ -24,153 +23,49 @@ fn part1(input: &str) -> usize {
|
||||||
.find(|p| p.pipe_type == PipeType::Starting)
|
.find(|p| p.pipe_type == PipeType::Starting)
|
||||||
.expect("no starting pipe found");
|
.expect("no starting pipe found");
|
||||||
|
|
||||||
let fist_step = starting_pipe.get_neg_x(&pipes);
|
let mut starting = Vec::with_capacity(2);
|
||||||
|
if starting_pipe.pipe_type.has_pos_x() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_pos_x(&pipes) {
|
||||||
|
starting.push((Direction::Left, pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_neg_x() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_neg_x(&pipes) {
|
||||||
|
starting.push((Direction::Right, pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_pos_y() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_pos_y(&pipes) {
|
||||||
|
starting.push((Direction::Up, pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_neg_y() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_neg_y(&pipes) {
|
||||||
|
starting.push((Direction::Down, pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let result = fist_step
|
let (path_a, path_b) = starting
|
||||||
.and_then(|starting_pipe| {
|
.into_iter()
|
||||||
bfs(
|
.map(|p| iter::successors(Some(p), |(d, p)| p.successor(*d, &pipes)))
|
||||||
&starting_pipe,
|
.collect_tuple()
|
||||||
|pipe| pipe.successors(&pipes),
|
.expect("more than 2 paths");
|
||||||
|pipe| pipe.pipe_type == PipeType::Starting,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.expect("no loop found");
|
|
||||||
|
|
||||||
result.len() / 2
|
path_a
|
||||||
|
.zip(path_b)
|
||||||
|
.take_while(|((_, pipe_a), (_, pipe_b))| pipe_a != pipe_b)
|
||||||
|
.count()
|
||||||
|
+ 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
||||||
enum Direction {
|
enum Direction {
|
||||||
None,
|
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VPipe(Pipe, Mutex<Direction>);
|
|
||||||
impl VPipe {
|
|
||||||
fn new(x: usize, y: usize, pipe_type: PipeType) -> Self {
|
|
||||||
Self(Pipe::new(x, y, pipe_type), Mutex::new(Direction::None))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn was_visited(&self) -> bool {
|
|
||||||
self.get_direction() != Direction::None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_direction(&self, direction: Direction) {
|
|
||||||
*self.1.lock().unwrap() = direction;
|
|
||||||
}
|
|
||||||
fn get_direction(&self) -> Direction {
|
|
||||||
*self.1.lock().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::ops::Deref for VPipe {
|
|
||||||
type Target = Pipe;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PartialEq for VPipe {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0 == other.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for VPipe {}
|
|
||||||
impl std::hash::Hash for VPipe {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.0.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Debug for VPipe {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("VPipe")
|
|
||||||
.field("pipe", &self.0)
|
|
||||||
.field("entered_from", &self.get_direction())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Clone for VPipe {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(
|
|
||||||
Pipe::new(self.x, self.y, self.pipe_type),
|
|
||||||
Mutex::new(self.get_direction()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VPipe {
|
|
||||||
fn successors<'a>(&self, pipes: &'a [Vec<VPipe>]) -> Vec<&'a VPipe> {
|
|
||||||
vec![
|
|
||||||
self.get_pos_x(pipes),
|
|
||||||
self.get_neg_x(pipes),
|
|
||||||
self.get_pos_y(pipes),
|
|
||||||
self.get_neg_y(pipes),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pos_x<'a>(&self, pipes: &'a [Vec<VPipe>]) -> Option<&'a VPipe> {
|
|
||||||
if self.pipe_type.has_pos_x() && self.get_direction() != Direction::Right {
|
|
||||||
pipes
|
|
||||||
.get(self.y)?
|
|
||||||
.get(self.x + 1)
|
|
||||||
.filter(|p| p.pipe_type.has_neg_x() && !p.was_visited())
|
|
||||||
.map(|p| {
|
|
||||||
p.set_direction(Direction::Left);
|
|
||||||
p
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_neg_x<'a>(&self, pipes: &'a [Vec<VPipe>]) -> Option<&'a VPipe> {
|
|
||||||
if self.pipe_type.has_neg_x() && self.get_direction() != Direction::Left {
|
|
||||||
pipes
|
|
||||||
.get(self.y)?
|
|
||||||
.get(self.x.checked_sub(1)?)
|
|
||||||
.filter(|p| p.pipe_type.has_pos_x() && !p.was_visited())
|
|
||||||
.map(|p| {
|
|
||||||
p.set_direction(Direction::Right);
|
|
||||||
p
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_pos_y<'a>(&self, pipes: &'a [Vec<VPipe>]) -> Option<&'a VPipe> {
|
|
||||||
if self.pipe_type.has_pos_y() && self.get_direction() != Direction::Down {
|
|
||||||
pipes
|
|
||||||
.get(self.y + 1)?
|
|
||||||
.get(self.x)
|
|
||||||
.filter(|p| p.pipe_type.has_neg_y() && !p.was_visited())
|
|
||||||
.map(|p| {
|
|
||||||
p.set_direction(Direction::Up);
|
|
||||||
p
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_neg_y<'a>(&self, pipes: &'a [Vec<VPipe>]) -> Option<&'a VPipe> {
|
|
||||||
if self.pipe_type.has_neg_y() && self.get_direction() != Direction::Up {
|
|
||||||
pipes
|
|
||||||
.get(self.y.checked_sub(1)?)?
|
|
||||||
.get(self.x)
|
|
||||||
.filter(|p| p.pipe_type.has_pos_y() && !p.was_visited())
|
|
||||||
.map(|p| {
|
|
||||||
p.set_direction(Direction::Down);
|
|
||||||
p
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
||||||
struct Pipe {
|
struct Pipe {
|
||||||
x: usize,
|
x: usize,
|
||||||
|
@ -181,6 +76,65 @@ impl Pipe {
|
||||||
fn new(x: usize, y: usize, pipe_type: PipeType) -> Self {
|
fn new(x: usize, y: usize, pipe_type: PipeType) -> Self {
|
||||||
Self { x, y, pipe_type }
|
Self { x, y, pipe_type }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn successor<'a>(
|
||||||
|
&self,
|
||||||
|
entry_direction: Direction,
|
||||||
|
pipes: &'a [Vec<Pipe>],
|
||||||
|
) -> Option<(Direction, &'a Self)> {
|
||||||
|
if entry_direction != Direction::Right && self.pipe_type.has_pos_x() {
|
||||||
|
self.get_pos_x(pipes).map(|p| (Direction::Left, p))
|
||||||
|
} else if entry_direction != Direction::Left && self.pipe_type.has_neg_x() {
|
||||||
|
self.get_neg_x(pipes).map(|p| (Direction::Right, p))
|
||||||
|
} else if entry_direction != Direction::Up && self.pipe_type.has_neg_y() {
|
||||||
|
self.get_neg_y(pipes).map(|p| (Direction::Down, p))
|
||||||
|
} else if entry_direction != Direction::Down && self.pipe_type.has_pos_y() {
|
||||||
|
self.get_pos_y(pipes).map(|p| (Direction::Up, p))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pos_x<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_pos_x() {
|
||||||
|
pipes
|
||||||
|
.get(self.y)?
|
||||||
|
.get(self.x + 1)
|
||||||
|
.filter(|p| p.pipe_type.has_neg_x())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_neg_x<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_neg_x() {
|
||||||
|
pipes
|
||||||
|
.get(self.y)?
|
||||||
|
.get(self.x.checked_sub(1)?)
|
||||||
|
.filter(|p| p.pipe_type.has_pos_x())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_pos_y<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_pos_y() {
|
||||||
|
pipes
|
||||||
|
.get(self.y + 1)?
|
||||||
|
.get(self.x)
|
||||||
|
.filter(|p| p.pipe_type.has_neg_y())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_neg_y<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_neg_y() {
|
||||||
|
pipes
|
||||||
|
.get(self.y.checked_sub(1)?)?
|
||||||
|
.get(self.x)
|
||||||
|
.filter(|p| p.pipe_type.has_pos_y())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
||||||
|
@ -246,7 +200,6 @@ mod tests {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "wrong starting pipe chosen in code"]
|
|
||||||
fn test_part1() {
|
fn test_part1() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
part1(indoc!(
|
part1(indoc!(
|
||||||
|
|
|
@ -0,0 +1,301 @@
|
||||||
|
use std::{collections::BTreeMap, iter};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("{}", part2(include_str!("./input.txt")));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part2(input: &str) -> usize {
|
||||||
|
let pipes = input
|
||||||
|
.lines()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(y, line)| {
|
||||||
|
line.char_indices()
|
||||||
|
.map(|(x, c)| Pipe::new(x, y, c.try_into().expect("invalid pipe character")))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Vec<_>>>();
|
||||||
|
|
||||||
|
let starting_pipe = pipes
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.find(|p| p.pipe_type == PipeType::Starting)
|
||||||
|
.expect("no starting pipe found");
|
||||||
|
|
||||||
|
let mut starting_directions = Vec::new();
|
||||||
|
|
||||||
|
let mut starting = Vec::with_capacity(2);
|
||||||
|
if starting_pipe.pipe_type.has_pos_x() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_pos_x(&pipes) {
|
||||||
|
starting.push((Direction::Left, pipe));
|
||||||
|
starting_directions.push(Direction::Right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_neg_x() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_neg_x(&pipes) {
|
||||||
|
starting.push((Direction::Right, pipe));
|
||||||
|
starting_directions.push(Direction::Left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_pos_y() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_pos_y(&pipes) {
|
||||||
|
starting.push((Direction::Up, pipe));
|
||||||
|
starting_directions.push(Direction::Down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if starting_pipe.pipe_type.has_neg_y() {
|
||||||
|
if let Some(pipe) = starting_pipe.get_neg_y(&pipes) {
|
||||||
|
starting.push((Direction::Down, pipe));
|
||||||
|
starting_directions.push(Direction::Up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (path_a, path_b) = starting
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| iter::successors(Some(p), |(d, p)| p.successor(*d, &pipes)))
|
||||||
|
.collect_tuple()
|
||||||
|
.expect("more than 2 paths");
|
||||||
|
|
||||||
|
let starting_pipe_type = match starting_directions
|
||||||
|
.into_iter()
|
||||||
|
.collect_tuple()
|
||||||
|
.expect("more than than two paths")
|
||||||
|
{
|
||||||
|
(Direction::Down, Direction::Up) => PipeType::Vertical,
|
||||||
|
(Direction::Right, Direction::Left) => PipeType::Horizontal,
|
||||||
|
(Direction::Right, Direction::Down) => PipeType::SouthEast,
|
||||||
|
(Direction::Right, Direction::Up) => PipeType::NorthEast,
|
||||||
|
(Direction::Left, Direction::Down) => PipeType::SouthWest,
|
||||||
|
(Direction::Left, Direction::Up) => PipeType::NorthWest,
|
||||||
|
_ => unreachable!("pipe type does not exist"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path_elements = BTreeMap::new();
|
||||||
|
path_elements.insert((starting_pipe.x, starting_pipe.y), starting_pipe_type);
|
||||||
|
|
||||||
|
let (a, b): (Vec<_>, Vec<_>) = path_a
|
||||||
|
.zip(path_b)
|
||||||
|
.take_while(|((_, pipe_a), (_, pipe_b))| pipe_a != pipe_b)
|
||||||
|
.unzip();
|
||||||
|
let mut path = a.into_iter().chain(b).collect::<Vec<_>>();
|
||||||
|
let (last_direction, last_pipe) = path.last().expect("no last element");
|
||||||
|
path.push(
|
||||||
|
last_pipe
|
||||||
|
.successor(*last_direction, &pipes)
|
||||||
|
.expect("no successor"),
|
||||||
|
);
|
||||||
|
path.iter().for_each(|(_, pipe)| {
|
||||||
|
path_elements.insert((pipe.x, pipe.y), pipe.pipe_type);
|
||||||
|
});
|
||||||
|
|
||||||
|
let width = pipes.get(0).expect("no pipes").len();
|
||||||
|
|
||||||
|
let inner = pipes
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|p| {
|
||||||
|
if path_elements.contains_key(&(p.x, p.y)) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let amount = (p.x..width)
|
||||||
|
.filter(|x| {
|
||||||
|
let pipe = path_elements.get(&(*x, p.y));
|
||||||
|
pipe.map(|pipe| {
|
||||||
|
matches!(
|
||||||
|
*pipe,
|
||||||
|
PipeType::Vertical | PipeType::NorthEast | PipeType::NorthWest
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
amount % 2 == 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
inner.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
||||||
|
enum Direction {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
||||||
|
struct Pipe {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
pipe_type: PipeType,
|
||||||
|
}
|
||||||
|
impl Pipe {
|
||||||
|
fn new(x: usize, y: usize, pipe_type: PipeType) -> Self {
|
||||||
|
Self { x, y, pipe_type }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn successor<'a>(
|
||||||
|
&self,
|
||||||
|
entry_direction: Direction,
|
||||||
|
pipes: &'a [Vec<Pipe>],
|
||||||
|
) -> Option<(Direction, &'a Self)> {
|
||||||
|
if entry_direction != Direction::Right && self.pipe_type.has_pos_x() {
|
||||||
|
self.get_pos_x(pipes).map(|p| (Direction::Left, p))
|
||||||
|
} else if entry_direction != Direction::Left && self.pipe_type.has_neg_x() {
|
||||||
|
self.get_neg_x(pipes).map(|p| (Direction::Right, p))
|
||||||
|
} else if entry_direction != Direction::Up && self.pipe_type.has_neg_y() {
|
||||||
|
self.get_neg_y(pipes).map(|p| (Direction::Down, p))
|
||||||
|
} else if entry_direction != Direction::Down && self.pipe_type.has_pos_y() {
|
||||||
|
self.get_pos_y(pipes).map(|p| (Direction::Up, p))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pos_x<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_pos_x() {
|
||||||
|
pipes
|
||||||
|
.get(self.y)?
|
||||||
|
.get(self.x + 1)
|
||||||
|
.filter(|p| p.pipe_type.has_neg_x())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_neg_x<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_neg_x() {
|
||||||
|
pipes
|
||||||
|
.get(self.y)?
|
||||||
|
.get(self.x.checked_sub(1)?)
|
||||||
|
.filter(|p| p.pipe_type.has_pos_x())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_pos_y<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_pos_y() {
|
||||||
|
pipes
|
||||||
|
.get(self.y + 1)?
|
||||||
|
.get(self.x)
|
||||||
|
.filter(|p| p.pipe_type.has_neg_y())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_neg_y<'a>(&self, pipes: &'a [Vec<Pipe>]) -> Option<&'a Pipe> {
|
||||||
|
if self.pipe_type.has_neg_y() {
|
||||||
|
pipes
|
||||||
|
.get(self.y.checked_sub(1)?)?
|
||||||
|
.get(self.x)
|
||||||
|
.filter(|p| p.pipe_type.has_pos_y())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
||||||
|
enum PipeType {
|
||||||
|
Empty,
|
||||||
|
Starting,
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
NorthEast,
|
||||||
|
NorthWest,
|
||||||
|
SouthWest,
|
||||||
|
SouthEast,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipeType {
|
||||||
|
fn has_pos_x(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Horizontal | Self::NorthEast | Self::SouthEast | Self::Starting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn has_neg_x(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Horizontal | Self::NorthWest | Self::SouthWest | Self::Starting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn has_pos_y(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Vertical | Self::SouthEast | Self::SouthWest | Self::Starting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn has_neg_y(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Vertical | Self::NorthEast | Self::NorthWest | Self::Starting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for PipeType {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(c: char) -> Result<Self, Self::Error> {
|
||||||
|
match c {
|
||||||
|
'.' => Ok(Self::Empty),
|
||||||
|
'S' => Ok(Self::Starting),
|
||||||
|
'-' => Ok(Self::Horizontal),
|
||||||
|
'|' => Ok(Self::Vertical),
|
||||||
|
'L' => Ok(Self::NorthEast),
|
||||||
|
'J' => Ok(Self::NorthWest),
|
||||||
|
'7' => Ok(Self::SouthWest),
|
||||||
|
'F' => Ok(Self::SouthEast),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part2() {
|
||||||
|
// assert_eq!(
|
||||||
|
// part2(indoc!(
|
||||||
|
// "
|
||||||
|
// 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
|
||||||
|
// "
|
||||||
|
// )),
|
||||||
|
// 10
|
||||||
|
// );
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
part2(indoc!(
|
||||||
|
"
|
||||||
|
.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...
|
||||||
|
"
|
||||||
|
)),
|
||||||
|
8
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue