diff --git a/src/bin/09.rs b/src/bin/09.rs index 83eafbf..2cdc3cc 100644 --- a/src/bin/09.rs +++ b/src/bin/09.rs @@ -1,23 +1,69 @@ use aoc::*; use derive_more::Deref; -use glam::U64Vec2; +use glam::{I64Vec2, IVec2, USizeVec2}; use itertools::Itertools; +use std::cmp::Reverse; +use std::collections::{HashMap, HashSet}; use std::ops::Sub; const INPUT: &str = include_str!("../../input/09"); fn main() { - println!("Part 1: {}", part1(INPUT)); - println!("Part 2: {}", part2(INPUT)); + let polygon = Polygon::parse(include_str!("../../input/09-test")); + // let polygon = Polygon::parse("2,2\n2,5\n5,5\n5,2\n"); + // let polygon = Polygon::parse("0,0\n2,0\n2,2\n0,2\n"); + // for line in polygon.vertical_lines() { + // println!("{line:?} {}", line.winding()); + // } + let (polygon, uncompress) = polygon.compress(); + print_corners(&polygon); + println!("==="); + print_area(&polygon); + // println!("Part 1: {}", part1(INPUT)); + // let p2 = part2(INPUT); + // println!("Part 2: {}", p2); + // assert!(p2 < 4583207265); +} + +fn print_corners(p: &Polygon) { + for y in 0..=10 { + for x in 0..=20 { + let draw = p.contains(&I64Vec2::new(x, y)); + if draw { + print!("#"); + } else { + print!("."); + } + } + println!(); + } +} + +fn print_area(p: &Polygon) { + for y in 0..=10 { + for x in 0..=20 { + let draw = p.contains_point(I64Vec2::new(x, y)); + if draw { + print!("#"); + } else { + print!("."); + } + } + println!(); + } } #[test] fn example() { assert_example!(part1, "09-test", 50); - assert!(Line(U64Vec2::new(0, 10), U64Vec2::ZERO).contains(U64Vec2::new(0, 0))); - assert!(Line(U64Vec2::new(0, 10), U64Vec2::ZERO).contains(U64Vec2::new(0, 10))); - assert!(!Line(U64Vec2::new(0, 10), U64Vec2::ZERO).contains(U64Vec2::new(0, 11))); - assert!(!Line(U64Vec2::new(0, 10), U64Vec2::ZERO).contains(U64Vec2::new(1, 10))); + assert!(Line(I64Vec2::new(0, 10), I64Vec2::ZERO).contains(I64Vec2::new(0, 0))); + assert!(Line(I64Vec2::new(0, 10), I64Vec2::ZERO).contains(I64Vec2::new(0, 10))); + assert!(Line(I64Vec2::new(0, 10), I64Vec2::ZERO).contains(I64Vec2::new(0, 5))); + assert!(!Line(I64Vec2::new(0, 10), I64Vec2::ZERO).contains(I64Vec2::new(0, 11))); + assert!(!Line(I64Vec2::new(0, 10), I64Vec2::ZERO).contains(I64Vec2::new(1, 10))); + assert!(!Line(I64Vec2::ONE, I64Vec2::ONE).contains(I64Vec2::new(1, 10))); + assert!(Line(I64Vec2::ONE, I64Vec2::ONE).contains(I64Vec2::new(1, 1))); + assert!(!Line(I64Vec2::ONE, I64Vec2::ONE).contains(I64Vec2::ZERO)); assert_example!(part2, "09-test", 24); } @@ -34,99 +80,251 @@ fn part1(input: &str) -> u64 { fn part2(input: &str) -> u64 { let polygon = Polygon::parse(input); + let (polygon, uncompress) = polygon.compress(); polygon .iter() .copied() .tuple_combinations() .map(Rect::from) - .filter(|&r| polygon.contains_rect(r)) - .map(Rect::area) - .max() + .sorted_by_key(|r| { + let area = r.uncompress(&uncompress).area(); + Reverse(area) + }) + .find(|&r| polygon.contains_rect(r)) .unwrap() + .area() } #[derive(Deref)] -struct Polygon(Vec); +struct Polygon(Vec); + +/// Uncompress a polygon with this. +/// Maps from compressed coords to actual coords. +struct Uncompress { + x: HashMap, + y: HashMap, +} impl Polygon { fn parse(input: &str) -> Self { Self(input.lines().map(parse_line).collect()) } + fn compress(mut self) -> (Self, Uncompress) { + let compress_x: HashMap = self + .0 + .iter() + .map(|p| p.x) + .unique() + .sorted() + .enumerate() + .map(|(i, x)| (x, i as i64)) + .collect(); + let compress_y: HashMap = self + .0 + .iter() + .map(|p| p.y) + .unique() + .sorted() + .enumerate() + .map(|(i, y)| (y, i as i64)) + .collect(); + self.0 = self + .0 + .into_iter() + .map(|p| I64Vec2::new(compress_x[&p.x], compress_y[&p.y])) + .collect(); + let uncompress_x = compress_x.into_iter().map(|(x, i)| (i, x)).collect(); + let uncompress_y = compress_y.into_iter().map(|(y, i)| (i, y)).collect(); + + ( + self, + Uncompress { + x: uncompress_x, + y: uncompress_y, + }, + ) + } + fn contains_rect(&self, r: Rect) -> bool { - r.all_points().into_iter().all(|p| self.contains_point(p)) + r.outline().into_iter().all(|p| self.contains_point(p)) } /// Raycast from left to right and check intersections with lines. - fn contains_point(&self, p: U64Vec2) -> bool { - let lines = self.lines(); - let is_on_line = |p| lines.iter().any(|line| line.contains(p)); + fn contains_point(&self, p: I64Vec2) -> bool { + // Interesting values for x + let xs = self + .vertical_lines() + .into_iter() + .filter(|l| { + let ymin = l.0.y.min(l.1.y); + let ymax = l.0.y.max(l.1.y); + ymin <= p.y && p.y <= ymax + }) + .flat_map(|l| [l.0.x - 1, l.0.x]) + .filter(|&x| x < p.x) + .chain([p.x]) + .sorted(); - let mut inside = false; - let mut was_on_line = false; + let lines = self.vertical_lines(); + let line = |p| lines.iter().find(|line| line.contains(p)).copied(); - for x in 0..=p.x { - let check = U64Vec2::new(x, p.y); - let is_on_line = is_on_line(check); - let crossed = !is_on_line && was_on_line; - if crossed { - inside ^= true; + let mut winding = 0; + let mut on_line = false; + + for x in xs { + let check = I64Vec2::new(x, p.y); + if let Some(line) = line(check) { + winding = line.winding(); + on_line = true; + } else { + on_line = false; } - was_on_line = is_on_line; } - inside || was_on_line + winding < 0 || on_line } - fn lines(&self) -> Vec { + fn vertical_lines(&self) -> Vec { self.iter() .chain(self.first()) // close the loop .copied() - .tuple_combinations() + .tuple_windows() .map(|(a, b)| Line(a, b)) + .filter(|l| l.is_vertical()) .collect() } + + // fn area(&self) -> HashSet { + // let (xmin, xmax) = self.0.iter().map(|p| p.x).minmax().into_option().unwrap(); + // let (ymin, ymax) = self.0.iter().map(|p| p.y).minmax().into_option().unwrap(); + // + // let mut result = HashSet::new(); + // for x in xmin..=xmax { + // for y in ymin..=ymax { + // let p = I64Vec2::new(x, y); + // if self.contains_point(p) { + // result.insert(p); + // } + // } + // } + // result + // } } -#[derive(Clone, Copy)] -struct Line(U64Vec2, U64Vec2); +#[derive(Clone, Copy, Debug)] +struct Line(I64Vec2, I64Vec2); impl Line { /// true if p on Line(a, b) - fn contains(&self, p: U64Vec2) -> bool { - let a = self.0.sub(p).length_squared(); - let b = self.1.sub(p).length_squared(); - let c = self.0.sub(self.1).length_squared(); - a + b == c + fn contains(self, p: I64Vec2) -> bool { + if self.is_vertical() { + let in_plane = p.x == self.0.x; + let ymin = self.0.y.min(self.1.y); + let ymax = self.0.y.max(self.1.y); + let in_bounds = ymin <= p.y && p.y <= ymax; + in_bounds && in_plane + } else { + let in_plane = p.y == self.0.y; + let xmin = self.0.x.min(self.1.x); + let xmax = self.0.x.max(self.1.x); + let in_bounds = xmin <= p.x && p.x <= xmax; + in_bounds && in_plane + } + // let a = self.0.sub(p).length_squared(); + // let b = self.1.sub(p).length_squared(); + // let c = self.0.sub(self.1).length_squared(); + // a + b == c + } + + fn is_vertical(self) -> bool { + self.0.x == self.1.x + } + + fn winding(self) -> i64 { + (self.1.y - self.0.y).signum() + } + + fn all_points(self) -> Vec { + if self.is_vertical() { + let ymin = self.0.y.min(self.1.y); + let ymax = self.0.y.max(self.1.y); + let x = self.0.x; + (ymin..=ymax).map(|y| I64Vec2::new(x, y)).collect() + } else { + let xmin = self.0.x.min(self.1.x); + let xmax = self.0.x.max(self.1.x); + let y = self.0.y; + (xmin..=xmax).map(|x| I64Vec2::new(x, y)).collect() + } } } -#[derive(Copy, Clone)] -struct Rect(U64Vec2, U64Vec2); +fn uncompress_i64vec2(v: I64Vec2, uncompress: &Uncompress) -> I64Vec2 { + I64Vec2::new(uncompress.x[&v.x], uncompress.y[&v.y]) +} +#[derive(Copy, Clone)] +struct Rect(I64Vec2, I64Vec2); impl Rect { fn area(self) -> u64 { (self.0.x.abs_diff(self.1.x) + 1) * (self.0.y.abs_diff(self.1.y) + 1) } - fn from((a, b): (U64Vec2, U64Vec2)) -> Self { + fn uncompress(self, uncompress: &Uncompress) -> Self { + Self( + uncompress_i64vec2(self.0, uncompress), + uncompress_i64vec2(self.1, uncompress), + ) + } + + fn from((a, b): (I64Vec2, I64Vec2)) -> Self { Self(a, b) } - fn all_points(self) -> Vec { + fn corners(self) -> [I64Vec2; 4] { + [ + I64Vec2::new(self.0.x, self.0.y), + I64Vec2::new(self.1.x, self.0.y), + I64Vec2::new(self.0.x, self.1.y), + I64Vec2::new(self.1.x, self.1.y), + ] + } + + fn all_points(self) -> Vec { let mut result = Vec::new(); for x in self.0.x.min(self.1.x)..=self.0.x.max(self.1.x) { for y in self.0.y.min(self.1.y)..=self.0.y.max(self.1.y) { - result.push(U64Vec2::new(x, y)); + result.push(I64Vec2::new(x, y)); } } result } + + fn lines(self) -> [Line; 4] { + let x1 = self.0.x.min(self.1.x); + let x2 = self.0.x.max(self.1.x); + let y1 = self.0.y.min(self.1.y); + let y2 = self.0.y.max(self.1.y); + [ + Line(I64Vec2::new(x1, y1), I64Vec2::new(x2, y1)), + Line(I64Vec2::new(x2, y1), I64Vec2::new(x2, y2)), + Line(I64Vec2::new(x2, y2), I64Vec2::new(x1, y2)), + Line(I64Vec2::new(x1, y2), I64Vec2::new(x1, y1)), + ] + } + + fn outline(self) -> Vec { + self.lines() + .into_iter() + .flat_map(|l| l.all_points()) + .collect() + } } -fn parse_line(line: &str) -> U64Vec2 { +fn parse_line(line: &str) -> I64Vec2 { let (x, y) = line.split_once(',').unwrap(); - U64Vec2::new(x.parse().unwrap(), y.parse().unwrap()) + I64Vec2::new(x.parse().unwrap(), y.parse().unwrap()) }