use aoc::*; use derive_more::Deref; 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() { 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(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); } fn part1(input: &str) -> u64 { Polygon::parse(input) .0 .into_iter() .tuple_combinations() .map(Rect::from) .map(Rect::area) .max() .unwrap() } fn part2(input: &str) -> u64 { let polygon = Polygon::parse(input); let (polygon, uncompress) = polygon.compress(); polygon .iter() .copied() .tuple_combinations() .map(Rect::from) .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); /// 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.outline().into_iter().all(|p| self.contains_point(p)) } /// Raycast from left to right and check intersections with lines. 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 lines = self.vertical_lines(); let line = |p| lines.iter().find(|line| line.contains(p)).copied(); 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; } } winding < 0 || on_line } fn vertical_lines(&self) -> Vec { self.iter() .chain(self.first()) // close the loop .copied() .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, Debug)] struct Line(I64Vec2, I64Vec2); impl Line { /// true if p on Line(a, b) 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() } } } 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 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 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(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) -> I64Vec2 { let (x, y) = line.split_once(',').unwrap(); I64Vec2::new(x.parse().unwrap(), y.parse().unwrap()) }