2023 - Day 10

This commit is contained in:
2023-12-10 20:23:57 +01:00
parent d0e6c3c4ad
commit 7734633c9f
8 changed files with 704 additions and 0 deletions

516
2023/src/bin/day10.rs Normal file
View File

@@ -0,0 +1,516 @@
#![feature(test)]
use core::panic;
use std::collections::HashMap;
use anyhow::Result;
use aoc::Solver;
// -- Runners --
fn main() -> Result<()> {
Day::solve()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_test1() -> Result<()> {
Day::test(Day::part1, "test-1", 4)
}
#[test]
fn part1_test2() -> Result<()> {
Day::test(Day::part1, "test-2", 8)
}
#[test]
fn part1_solution() -> Result<()> {
Day::test(Day::part1, "input", 6768)
}
#[test]
fn part2_test1() -> Result<()> {
Day::test(Day::part2, "test-1", 1)
}
#[test]
fn part2_test2() -> Result<()> {
Day::test(Day::part2, "test-2", 1)
}
#[test]
fn part2_test3() -> Result<()> {
Day::test(Day::part2, "test-3", 4)
}
#[test]
fn part2_test4() -> Result<()> {
Day::test(Day::part2, "test-4", 4)
}
#[test]
fn part2_test5() -> Result<()> {
Day::test(Day::part2, "test-5", 8)
}
#[test]
fn part2_test6() -> Result<()> {
Day::test(Day::part2, "test-6", 10)
}
#[test]
fn part2_solution() -> Result<()> {
Day::test(Day::part2, "input", 351)
}
// Benchmarks
extern crate test;
#[bench]
#[ignore]
fn part1_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part1, b)
}
#[bench]
#[ignore]
fn part2_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part2, b)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Maze {
Vertical,
Horizontal,
NorthEast,
NorthWest,
SouthWest,
SouthEast,
Start,
}
impl Maze {
fn from(c: char) -> Option<Self> {
match c {
'|' => Some(Self::Vertical),
'-' => Some(Self::Horizontal),
'L' => Some(Self::NorthEast),
'J' => Some(Self::NorthWest),
'7' => Some(Self::SouthWest),
'F' => Some(Self::SouthEast),
'S' => Some(Self::Start),
_ => None,
}
}
fn connects(&self, other: &Self, direction: (isize, isize)) -> bool {
match direction {
(0, -1) => match self {
Self::Vertical | Self::NorthEast | Self::NorthWest | Self::Start => {
*other == Self::Vertical
|| *other == Self::SouthWest
|| *other == Self::SouthEast
|| *other == Self::Start
}
_ => false,
},
(1, 0) => match self {
Self::Horizontal | Self::NorthEast | Self::SouthEast | Self::Start => {
*other == Self::Horizontal
|| *other == Self::NorthWest
|| *other == Self::SouthWest
|| *other == Self::Start
}
_ => false,
},
(0, 1) => match self {
Self::Vertical | Self::SouthWest | Self::SouthEast | Self::Start => {
*other == Self::Vertical
|| *other == Self::NorthEast
|| *other == Self::NorthWest
|| *other == Self::Start
}
_ => false,
},
(-1, 0) => match self {
Self::Horizontal | Self::NorthWest | Self::SouthWest | Self::Start => {
*other == Self::Horizontal
|| *other == Self::NorthEast
|| *other == Self::SouthEast
|| *other == Self::Start
}
_ => false,
},
_ => unreachable!("Invalid direction"),
}
}
fn sides(&self, direction: (isize, isize)) -> (usize, usize) {
match direction {
(0, -1) => match self {
Maze::Vertical => (1, 1),
Maze::NorthEast => (2, 0),
Maze::NorthWest => (0, 2),
Maze::Start => (0, 0),
_ => unreachable!("Invalid movement"),
},
(1, 0) => match self {
Maze::Horizontal => (1, 1),
Maze::NorthEast => (0, 2),
Maze::SouthEast => (2, 0),
Maze::Start => (0, 0),
_ => unreachable!("Invalid movement"),
},
(0, 1) => match self {
Maze::Vertical => (1, 1),
Maze::SouthWest => (2, 0),
Maze::SouthEast => (0, 2),
Maze::Start => (0, 0),
_ => unreachable!("Invalid movement"),
},
(-1, 0) => match self {
Maze::Horizontal => (1, 1),
Maze::NorthWest => (2, 0),
Maze::SouthWest => (0, 2),
Maze::Start => (0, 0),
_ => unreachable!("Invalid movement"),
},
_ => unreachable!("Invalid direction"),
}
}
}
// #[derive(Debug, PartialEq, Eq, Clone, Copy)]
// enum Loop {
// North,
// East,
// South,
// West,
// Inner,
// }
//
// impl Loop {
// fn from(direction: (isize, isize)) -> Self {
// match direction {
// (0, -1) => Self::North,
// (1, 0) => Self::East,
// (0, 1) => Self::South,
// (-1, 0) => Self::West,
// _ => unreachable!("Invalid direction"),
// }
// }
//
// fn offset(&self) -> (isize, isize) {
// match self {
// Loop::North => (0, -1),
// Loop::East => (1, 0),
// Loop::South => (0, 1),
// Loop::West => (-1, 0),
// Loop::Inner => (0, 0),
// }
// }
// }
//
// fn flood_fill(position: (isize, isize), map: &mut HashMap<(isize, isize), Loop>) {
// if map.get(&position).is_none() {
// map.insert(position, Loop::Inner);
//
// for direction in DIRECTIONS {
// flood_fill((position.0 + direction.0, position.1 + direction.1), map)
// }
// }
// }
static DIRECTIONS: [(isize, isize); 4] = [(0, -1), (1, 0), (0, 1), (-1, 0)];
// -- Solution --
pub struct Day;
impl aoc::Solver for Day {
type Output1 = usize;
type Output2 = usize;
fn day() -> u8 {
10
}
fn part1(input: &str) -> Self::Output1 {
let mut position_start = (-1, -1);
let maze: HashMap<(isize, isize), Maze> = input
.lines()
.enumerate()
.flat_map(|(y, line)| {
line.chars()
.enumerate()
.filter_map(|(x, c)| Maze::from(c).map(|maze| ((x as isize, y as isize), maze)))
.inspect(|(position, maze)| {
if *maze == Maze::Start {
position_start = *position
}
})
.collect::<Vec<_>>()
})
.collect();
if position_start == (-1, -1) {
panic!("No valid start in input");
}
let mut position_current = position_start;
let mut position_previous = position_start;
let mut length = 0;
'outer: loop {
let tile_current = maze.get(&position_current).unwrap();
for direction in DIRECTIONS {
let position_next = (
position_current.0 + direction.0,
position_current.1 + direction.1,
);
// We are now allowed to go back to the previous position
if position_next == position_previous {
continue;
}
if let Some(tile_next) = maze.get(&position_next) {
if tile_current.connects(tile_next, direction) {
position_previous = position_current;
position_current = position_next;
length += 1;
if position_current == position_start {
return length / 2;
}
continue 'outer;
}
}
}
unreachable!("Unable to move forward...");
}
}
fn part2(input: &str) -> Self::Output2 {
let mut position_start = (-1, -1);
let maze: HashMap<(isize, isize), Maze> = input
.lines()
.enumerate()
.flat_map(|(y, line)| {
line.chars()
.enumerate()
.filter_map(|(x, c)| Maze::from(c).map(|maze| ((x as isize, y as isize), maze)))
.inspect(|(position, maze)| {
if *maze == Maze::Start {
position_start = *position
}
})
.collect::<Vec<_>>()
})
.collect();
if position_start == (-1, -1) {
panic!("No valid start in input");
}
let mut map = HashMap::<(isize, isize), Maze>::new();
let mut position_current = position_start;
let mut position_previous = position_start;
'outer: loop {
let tile_current = maze.get(&position_current).unwrap();
for direction in DIRECTIONS {
let position_next = (
position_current.0 + direction.0,
position_current.1 + direction.1,
);
// We are now allowed to go back to the previous position
if position_next == position_previous {
continue;
}
if let Some(tile_next) = maze.get(&position_next) {
if tile_current.connects(tile_next, direction) {
map.insert(position_current, *tile_current);
position_previous = position_current;
position_current = position_next;
if position_current == position_start {
break 'outer;
}
continue 'outer;
}
}
}
unreachable!("Unable to move forward...");
}
// It appears that the start can always be replaced with an F
// Atleast in all the examples and my input
map.insert(position_start, Maze::SouthEast);
let bounds = (
input.lines().map(|line| line.len()).max().unwrap(),
input.lines().count(),
);
let mut count = 0;
let mut in_loop = false;
let mut maybe = None;
for y in 0..bounds.1 {
for x in 0..bounds.0 {
if let Some(entry) = map.get(&(x as isize, y as isize)) {
match entry {
Maze::Vertical => in_loop = !in_loop,
Maze::Horizontal => {}
dir @ (Maze::NorthEast | Maze::SouthEast) => maybe = Some(*dir),
Maze::NorthWest => {
if maybe == Some(Maze::SouthEast) {
in_loop = !in_loop
}
}
Maze::SouthWest => {
if maybe == Some(Maze::NorthEast) {
in_loop = !in_loop
}
}
Maze::Start => unreachable!("Start should have been replaced"),
}
} else if in_loop {
count += 1;
}
}
}
count
}
// Alternate solution that works for the examples and is off-by-one for the actual input
// fn part2(input: &str) -> Self::Output2 {
// let mut position_start = (-1, -1);
// let maze: HashMap<(isize, isize), Maze> = input
// .lines()
// .enumerate()
// .flat_map(|(y, line)| {
// line.chars()
// .enumerate()
// .filter_map(|(x, c)| Maze::from(c).map(|maze| ((x as isize, y as isize), maze)))
// .inspect(|(position, maze)| {
// if *maze == Maze::Start {
// position_start = *position
// }
// })
// .collect::<Vec<_>>()
// })
// .collect();
//
// let mut map = HashMap::<(isize, isize), Loop>::new();
//
// if position_start == (-1, -1) {
// panic!("No valid start in input");
// }
//
// let mut sides = (0, 0);
//
// let mut position_current = position_start;
// let mut position_previous = position_start;
// 'outer: loop {
// let tile_current = maze.get(&position_current).unwrap();
//
// for direction in DIRECTIONS {
// let position_next = (
// position_current.0 + direction.0,
// position_current.1 + direction.1,
// );
//
// // We are now allowed to go back to the previous position
// if position_next == position_previous {
// continue;
// }
//
// if let Some(tile_next) = maze.get(&position_next) {
// if tile_current.connects(tile_next, direction) {
// map.insert(position_current, Loop::from(direction));
//
// position_previous = position_current;
// position_current = position_next;
//
// let s = tile_current.sides(direction);
// sides.0 += s.0;
// sides.1 += s.1;
//
// if position_current == position_start {
// break 'outer;
// }
//
// continue 'outer;
// }
// }
// }
//
// unreachable!("Unable to move forward...");
// }
//
// let clockwise = sides.0 > sides.1;
//
// let mut position_current = position_start;
// loop {
// let &direction = map.get(&position_current).unwrap();
//
// let check = match direction {
// Loop::North => {
// if clockwise {
// (1, 0)
// } else {
// (-1, 0)
// }
// }
// Loop::East => {
// if clockwise {
// (0, 1)
// } else {
// (0, -1)
// }
// }
// Loop::South => {
// if clockwise {
// (-1, 0)
// } else {
// (1, 0)
// }
// }
// Loop::West => {
// if clockwise {
// (0, -1)
// } else {
// (0, 1)
// }
// }
// Loop::Inner => unreachable!("Loop should not contain Inner"),
// };
// let check = (position_current.0 + check.0, position_current.1 + check.1);
//
// flood_fill(check, &mut map);
//
// let direction = direction.offset();
//
// position_current.0 += direction.0;
// position_current.1 += direction.1;
//
// if position_current == position_start {
// break;
// }
// }
//
// map.iter()
// .filter(|(_, &value)| value == Loop::Inner)
// .count()
// }
}