AoC23/src/day18.rs

676 lines
20 KiB
Rust

use std::{collections::VecDeque, ops::RangeInclusive, thread};
use itertools::{Itertools, MinMaxResult};
use regex::{Captures, Regex, Replacer};
pub fn run() {
let input = include_str!("../input/day18.txt");
// let input = r#"
// R 6 (#70c710)
// D 5 (#0dc571)
// L 2 (#5713f0)
// D 2 (#d2c081)
// R 2 (#59c680)
// D 2 (#411b91)
// L 5 (#8ceee2)
// U 2 (#caa173)
// L 1 (#1b58a2)
// U 2 (#caa171)
// R 2 (#7807d2)
// U 3 (#a77fa3)
// L 2 (#015232)
// U 2 (#7a21e3)
// "#;
// let input = r#"
// R 4 (#000000)
// U 4 (#000000)
// R 6 (#000000)
// U 10 (#000000)
// L 4 (#000000)
// D 6 (#000000)
// L 6 (#000000)
// D 8 (#000000)
// "#;
// let input = r#"
// L 3 (#000000)
// U 5 (#000000)
// L 3 (#000000)
// D 3 (#000000)
// L 3 (#000000)
// U 6 (#000000)
// R 9 (#000000)
// D 8 (#000000)
// "#;
let instructions = parse_instructions(input);
let path = run_instructions(&instructions);
dbg!(path_bounds(&path));
dbg!(count_enclosed2(&path, true));
// let instructions2 = parse_instructions_part2(input);
// let path2 = run_instructions(&instructions2);
// dbg!(count_enclosed2(&path2, false));
}
fn parse_instructions(s: &str) -> Vec<Instruction> {
s.trim()
.lines()
.map(|l| {
let mut parts = l.trim().split(" ");
let dir: Direction = parts.next().unwrap().into();
let distance: u32 = parts.next().unwrap().parse().unwrap();
Instruction {
direction: dir,
distance,
}
})
.collect()
}
fn parse_instructions_part2(s: &str) -> Vec<Instruction> {
s.trim()
.lines()
.map(|l| {
let hex = l
.trim()
.split(" ")
.skip(2)
.next()
.unwrap()
.strip_prefix("(#")
.unwrap()
.strip_suffix(")")
.unwrap();
let dir = match hex.chars().last().unwrap() {
'0' => Direction::Right,
'1' => Direction::Down,
'2' => Direction::Left,
'3' => Direction::Up,
c => panic!("unexpected dir {}", c),
};
let distance = u32::from_str_radix(hex.split_at(5).0, 16).unwrap();
Instruction {
direction: dir,
distance,
}
})
.collect()
}
#[derive(Debug, Clone, Copy)]
struct Instruction {
direction: Direction,
distance: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl From<&str> for Direction {
fn from(value: &str) -> Self {
match value {
"U" => Self::Up,
"D" => Self::Down,
"L" => Self::Left,
"R" => Self::Right,
_ => panic!("invalid direction {}", value),
}
}
}
impl Direction {
fn is_vertical(&self) -> bool {
match self {
Self::Up | Self::Down => true,
_ => false,
}
}
fn move_point(&self, (x, y): (isize, isize), distance: isize) -> (isize, isize) {
match self {
Self::Up => (x, y - distance),
Self::Down => (x, y + distance),
Self::Left => (x - distance, y),
Self::Right => (x + distance, y),
}
}
fn turn(&self, other: Self) -> Turn {
match (self, other) {
(Self::Up, Self::Left) => Turn::Left,
(Self::Up, Self::Right) => Turn::Right,
(Self::Up, Self::Up) => Turn::None,
(Self::Up, Self::Down) => Turn::Back,
(Self::Down, Self::Left) => Turn::Right,
(Self::Down, Self::Right) => Turn::Left,
(Self::Down, Self::Down) => Turn::None,
(Self::Down, Self::Up) => Turn::Back,
(Self::Left, Self::Up) => Turn::Right,
(Self::Left, Self::Down) => Turn::Left,
(Self::Left, Self::Left) => Turn::None,
(Self::Left, Self::Right) => Turn::Back,
(Self::Right, Self::Up) => Turn::Left,
(Self::Right, Self::Down) => Turn::Right,
(Self::Right, Self::Right) => Turn::None,
(Self::Right, Self::Left) => Turn::Back,
}
}
fn between(a: (isize, isize), b: (isize, isize)) -> Self {
if a.0 == b.0 {
if a.1 > b.1 {
Self::Up
} else {
Self::Down
}
} else if a.1 == b.1 {
if a.0 > b.0 {
Self::Left
} else {
Self::Right
}
} else {
panic!()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Turn {
None,
Back,
Left,
Right,
}
fn run_instructions(instructions: &[Instruction]) -> Vec<Segment> {
let mut path = vec![];
let mut prev = (0, 0);
for insn in instructions {
let next = insn.direction.move_point(prev, insn.distance as isize);
path.push(Segment {
start: prev,
end: next,
direction: insn.direction,
});
prev = next;
}
path
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Segment {
start: (isize, isize),
end: (isize, isize),
direction: Direction,
}
impl Segment {
fn contains(&self, (x, y): (isize, isize)) -> bool {
if self.start.0 == self.end.0 {
let min_y = self.start.1.min(self.end.1);
let max_y = self.start.1.max(self.end.1);
x == self.start.0 && y >= min_y && y <= max_y
} else if self.start.1 == self.end.1 {
let min_x = self.start.0.min(self.end.0);
let max_x = self.start.0.max(self.end.0);
x >= min_x && x <= max_x && y == self.start.1
} else {
panic!();
}
}
fn contains_y(&self, y: isize) -> bool {
let min_y = self.start.1.min(self.end.1);
let max_y = self.start.1.max(self.end.1);
y >= min_y && y <= max_y
}
fn intersects(&self, min_x: isize, max_x: isize, min_y: isize, max_y: isize) -> bool {
if self.direction.is_vertical() {
if self.start.0 >= min_x && self.start.0 <= max_x {
let ys = self.start.1.min(self.end.1)..=self.start.1.max(self.end.1);
return range_intersects(ys, min_y..=max_y);
}
} else {
if self.start.1 >= min_y && self.start.1 <= max_y {
let xs = self.start.0.min(self.end.0)..=self.start.0.max(self.end.0);
return range_intersects(xs, min_x..=max_x);
}
}
false
}
}
fn range_intersects(a: RangeInclusive<isize>, b: RangeInclusive<isize>) -> bool {
if b.contains(a.start()) {
true
} else if b.contains(a.end()) {
true
} else {
a.start() < b.start() && a.end() > b.end()
}
}
fn path_bounds(path: &[Segment]) -> (isize, isize, isize, isize) {
let xs = path
.iter()
.flat_map(|segment| [segment.start.0, segment.end.0].into_iter())
.minmax();
let (min_x, max_x) = match xs {
MinMaxResult::NoElements => panic!(),
MinMaxResult::OneElement(x) => (x, x),
MinMaxResult::MinMax(min, max) => (min, max),
};
let ys = path
.iter()
.flat_map(|segment| [segment.start.1, segment.end.1].into_iter())
.minmax();
let (min_y, max_y) = match ys {
MinMaxResult::NoElements => panic!(),
MinMaxResult::OneElement(y) => (y, y),
MinMaxResult::MinMax(min, max) => (min, max),
};
(min_x, max_x, min_y, max_y)
}
fn count_enclosed2(path: &[Segment], visualize: bool) -> usize {
// println!("initial segments: {}", path.len());
// print_path(path);
let mut count = 0;
let mut path = path.to_vec();
let orig_bounds = path_bounds(&path);
dbg!(orig_bounds);
let mut last_segment_count = None;
let mut iteration = 0;
loop {
if visualize {
write_visualization(&path, orig_bounds, iteration);
}
iteration += 1;
// println!("--------------");
// print_path(&path);
if let Some(c) = last_segment_count {
if c == path.len() {
print_path(&path);
panic!("stuck at {} segments", c);
} else {
last_segment_count = Some(path.len());
}
} else {
last_segment_count = Some(path.len());
}
// println!("segments remaining: {}", path.len());
// if iterations == 4 {
// break;
// }
if path.len() > 4 {
'start_idx: for start_idx in 0..path.len() - 3 {
let a = path[start_idx];
let b = path[start_idx + 1];
let c = path[start_idx + 2];
let ab = a.direction.turn(b.direction);
let bc = b.direction.turn(c.direction);
if !((ab == Turn::Right && bc == Turn::Right)
|| (ab == Turn::Left && bc == Turn::Left))
{
continue;
}
let min_x: isize;
let max_x: isize;
let min_y: isize;
let max_y: isize;
if !b.direction.is_vertical() {
if b.direction == Direction::Left {
min_x = b.end.0;
max_x = b.start.0;
} else {
min_x = b.start.0;
max_x = b.end.0;
}
let up: Segment;
let down: Segment;
if a.direction == Direction::Up {
up = a;
down = c;
} else {
up = c;
down = a;
}
min_y = up.end.1.max(down.start.1);
max_y = up.start.1.min(down.end.1);
// skip this start_idx if removing the rect would cause the path to intersect
// itself
for (i, s) in path.iter().enumerate() {
if i == start_idx || i == start_idx + 1 || i == start_idx + 2 {
continue;
}
if b.direction.is_vertical() == s.direction.is_vertical() {
continue;
}
if s.intersects(min_x, max_x, min_y, max_y) {
continue 'start_idx;
}
}
// dbg!(start_idx);
let width = 1 + max_x - min_x;
let height = 1 + max_y - min_y;
if is_inside(&path, (max_x, max_y - 1)) {
// println!("+{}", width * (height - 1));
count += width * (height - 1);
} else {
// println!("-{}", (width - 2) * (height - 1));
count -= (width - 2) * (height - 1);
}
if a.direction == Direction::Down {
path[start_idx].end.1 = min_y;
path[start_idx + 1].start.1 = min_y;
path[start_idx + 1].end.1 = min_y;
path[start_idx + 2].start.1 = min_y;
} else {
path[start_idx].end.1 = max_y;
path[start_idx + 1].start.1 = max_y;
path[start_idx + 1].end.1 = max_y;
path[start_idx + 2].start.1 = max_y;
}
} else {
if b.direction == Direction::Down {
min_y = b.start.1;
max_y = b.end.1;
} else {
min_y = b.end.1;
max_y = b.start.1;
}
let left: Segment;
let right: Segment;
if a.direction == Direction::Left {
left = a;
right = c;
} else {
left = c;
right = a;
}
min_x = left.end.0.max(right.start.0);
max_x = left.start.0.min(right.end.0);
// skip this start_idx if removing the rect would cause the path to intersect
// itself
for (i, s) in path.iter().enumerate() {
if i == start_idx || i == start_idx + 1 || i == start_idx + 2 {
continue;
}
if b.direction.is_vertical() == s.direction.is_vertical() {
continue;
}
if s.intersects(min_x, max_x, min_y, max_y) {
continue 'start_idx;
}
}
// dbg!(start_idx);
let width = 1 + max_x - min_x;
let height = 1 + max_y - min_y;
// dbg!((width, height));
if is_inside(&path, (max_x, max_y - 1)) {
// println!("+{}", (width - 1) * height);
count += (width - 1) * height;
} else {
// println!("-{}", (width - 1) * (height - 2));
count -= (width - 1) * (height - 2);
}
if a.direction == Direction::Left {
path[start_idx].end.0 = max_x;
path[start_idx + 1].start.0 = max_x;
path[start_idx + 1].end.0 = max_x;
path[start_idx + 2].start.0 = max_x;
} else {
path[start_idx].end.0 = min_x;
path[start_idx + 1].start.0 = min_x;
path[start_idx + 1].end.0 = min_x;
path[start_idx + 2].start.0 = min_x;
}
}
simplify_path(&mut path);
break;
}
} else if path.len() == 4 {
println!("four segments remaining, almost done");
let (min_x, max_x, min_y, max_y) = rect_around(path[0], path[1], path[2]);
let width = 1 + max_x - min_x;
let height = 1 + max_y - min_y;
// dbg!((width, height));
count += width * height;
break;
}
}
dbg!(iteration);
count as usize
}
fn is_inside(path: &[Segment], p: (isize, isize)) -> bool {
let (min_x, _, _, _) = path_bounds(path);
let row_paths = path
.iter()
.filter(|s| s.contains_y(p.1))
.collect::<Vec<_>>();
let mut inside = false;
for x in min_x..p.0 {
let containing = row_paths
.iter()
.filter(|s| s.contains((x, p.1)))
.take(2)
.collect::<Vec<_>>();
if containing.len() == 2 {
// only count the top corners
let vert = if containing[0].direction.is_vertical() {
containing[0]
} else {
containing[1]
};
let min_y = vert.start.1.min(vert.end.1);
if p.1 == min_y {
inside = !inside;
}
} else if containing.len() == 1 {
if containing[0].direction.is_vertical() {
inside = !inside;
}
}
}
inside
}
fn rect_around(a: Segment, b: Segment, c: Segment) -> (isize, isize, isize, isize) {
let mut min_x = a.start.0;
let mut max_x = a.start.0;
let mut min_y = a.start.1;
let mut max_y = a.start.1;
for s in [a, b, c] {
min_x = min_x.min(s.start.0).min(s.end.0);
max_x = max_x.max(s.start.0).max(s.end.0);
min_y = min_y.min(s.start.1).min(s.end.1);
max_y = max_y.max(s.start.1).max(s.end.1);
}
(min_x, max_x, min_y, max_y)
}
fn simplify_path(path: &mut Vec<Segment>) {
// println!("before simplification:");
// print_path(path);
let mut i = 0;
while i < path.len() - 1 {
let a = path[i];
let b = path[i + 1];
if b.start == b.end {
path.remove(i + 1);
continue;
}
path[i].direction = Direction::between(a.start, a.end);
let turn = a.direction.turn(b.direction);
match turn {
Turn::None | Turn::Back => {
if path[i].direction.is_vertical() {
path[i].end.1 = path[i + 1].end.1;
} else {
path[i].end.0 = path[i + 1].end.0;
}
path.remove(i + 1);
}
_ => i += 1,
}
}
assert_eq!(path[0].start, path[path.len() - 1].end);
if path[0].direction == path[path.len() - 1].direction {
path[0].start = path[path.len() - 1].start;
path.remove(path.len() - 1);
}
}
fn print_path(path: &[Segment]) {
// for segment in path {
// println!(
// "{:?} {:?} -> {:?}",
// segment.direction, segment.start, segment.end
// );
// }
let (min_x, max_x, min_y, max_y) = path_bounds(path);
for y in min_y..=max_y {
for x in min_x..=max_x {
if (x, y) == path[0].start {
print!("X");
} else if let Some(s) = path.iter().find(|s| s.contains((x, y))) {
let c = match s.direction {
Direction::Up => '^',
Direction::Down => 'V',
Direction::Left => '<',
Direction::Right => '>',
};
print!("{}", c);
} else {
print!(".");
}
}
println!();
}
println!();
}
fn write_visualization(
path: &[Segment],
(min_x, max_x, min_y, max_y): (isize, isize, isize, isize),
iteration: u32,
) {
let mut buf = image::ImageBuffer::new((1 + max_x - min_x) as u32, (1 + max_y - min_y) as u32);
for (x, y, pixel) in buf.enumerate_pixels_mut() {
let world_x = min_x + x as isize;
let world_y = min_y + y as isize;
if path.iter().any(|s| s.contains((world_x, world_y))) {
*pixel = image::Rgb([0u8, 0, 0]);
} else {
*pixel = image::Rgb([255, 255, 255]);
}
}
buf.save(format!("vis/{}.png", iteration)).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_run_instructions() {
let instructions = parse_instructions(
r#"
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
"#,
);
let path = run_instructions(&instructions);
assert_eq!(
path,
vec![
Segment {
start: (0, 0),
end: (6, 0),
direction: Direction::Right
},
Segment {
start: (6, 0),
end: (6, 5),
direction: Direction::Down
},
Segment {
start: (6, 5),
end: (4, 5),
direction: Direction::Left
},
Segment {
start: (4, 5),
end: (4, 7),
direction: Direction::Down
},
]
);
}
#[test]
fn test_count_inside() {
let instructions = parse_instructions(
r#"
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)
"#,
);
let path = run_instructions(&instructions);
assert_eq!(count_enclosed2(&path, false), 62);
}
}