This commit is contained in:
Moritz Hölting 2023-12-11 10:29:05 +01:00
parent 9133cd21a5
commit 61690cda6c
4 changed files with 395 additions and 276 deletions

133
Cargo.lock generated
View File

@ -118,20 +118,6 @@ version = "0.0.0"
dependencies = [
"indoc",
"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]]
@ -140,49 +126,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "indoc"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "itertools"
version = "0.12.0"
@ -299,39 +248,6 @@ dependencies = [
"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]]
name = "ranges"
version = "0.3.3"
@ -358,57 +274,8 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"

View File

@ -7,8 +7,6 @@ edition = "2021"
[dependencies]
itertools.workspace = true
pathfinding = "4.4.0"
rayon.workspace = true
[dev-dependencies]
indoc.workspace = true

View File

@ -1,7 +1,6 @@
use std::{fmt::Debug, sync::Mutex};
use std::iter;
use pathfinding::prelude::bfs;
use rayon::prelude::*;
use itertools::Itertools;
fn main() {
println!("{}", part1(include_str!("./input.txt")));
@ -12,8 +11,8 @@ fn part1(input: &str) -> usize {
.lines()
.enumerate()
.map(|(y, line)| {
line.par_char_indices()
.map(|(x, c)| VPipe::new(x, y, c.try_into().expect("invalid pipe character")))
line.char_indices()
.map(|(x, c)| Pipe::new(x, y, c.try_into().expect("invalid pipe character")))
.collect()
})
.collect::<Vec<Vec<_>>>();
@ -24,153 +23,49 @@ fn part1(input: &str) -> usize {
.find(|p| p.pipe_type == PipeType::Starting)
.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
.and_then(|starting_pipe| {
bfs(
&starting_pipe,
|pipe| pipe.successors(&pipes),
|pipe| pipe.pipe_type == PipeType::Starting,
)
})
.expect("no loop found");
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");
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)]
enum Direction {
None,
Left,
Right,
Up,
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)]
struct Pipe {
x: usize,
@ -181,6 +76,65 @@ 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)]
@ -246,7 +200,6 @@ mod tests {
use indoc::indoc;
#[test]
#[ignore = "wrong starting pipe chosen in code"]
fn test_part1() {
assert_eq!(
part1(indoc!(

301
day-10/src/bin/part2.rs Normal file
View File

@ -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
)
}
}