268 lines
7.6 KiB
Rust
268 lines
7.6 KiB
Rust
use aoc::*;
|
|
use glam::I64Vec2;
|
|
use itertools::Itertools;
|
|
use rayon::prelude::*;
|
|
use std::collections::HashMap;
|
|
|
|
const INPUT: &str = include_str!("../../input/09");
|
|
|
|
fn main() {
|
|
println!("Part 1: {}", part1(INPUT));
|
|
println!("Part 2: {}", part2(INPUT));
|
|
}
|
|
|
|
#[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)
|
|
.points
|
|
.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
|
|
.points
|
|
.iter()
|
|
.copied()
|
|
.tuple_combinations()
|
|
.par_bridge()
|
|
.map(Rect::from)
|
|
.filter(|&r| polygon.contains_rect(r))
|
|
.map(|r| r.uncompress(&uncompress).area())
|
|
.max()
|
|
.unwrap()
|
|
}
|
|
|
|
struct Polygon {
|
|
points: Vec<I64Vec2>,
|
|
/// Contains vertical lines sorted by their x value in ascending order.
|
|
/// This is important for contains_point, as that assumes the lines are in this order.
|
|
/// This dramatically speeds up the raycast in contains_point, as the ray is cast in positive x direction.
|
|
cached_vertical_lines: Vec<Line>,
|
|
}
|
|
|
|
/// Uncompress a polygon with this.
|
|
/// Maps from compressed coords to actual coords.
|
|
struct Uncompress {
|
|
x: HashMap<i64, i64>,
|
|
y: HashMap<i64, i64>,
|
|
}
|
|
|
|
impl Polygon {
|
|
fn parse(input: &str) -> Self {
|
|
let mut polygon = Self {
|
|
points: input.lines().map(parse_line).collect(),
|
|
cached_vertical_lines: Default::default(),
|
|
};
|
|
polygon.cache_vertical_lines();
|
|
polygon
|
|
}
|
|
|
|
fn compress(mut self) -> (Self, Uncompress) {
|
|
let compress_x: HashMap<i64, i64> = self
|
|
.points
|
|
.iter()
|
|
.map(|p| p.x)
|
|
.unique()
|
|
.sorted()
|
|
.enumerate()
|
|
.map(|(i, x)| (x, i as i64))
|
|
.collect();
|
|
let compress_y: HashMap<i64, i64> = self
|
|
.points
|
|
.iter()
|
|
.map(|p| p.y)
|
|
.unique()
|
|
.sorted()
|
|
.enumerate()
|
|
.map(|(i, y)| (y, i as i64))
|
|
.collect();
|
|
|
|
self.points = self
|
|
.points
|
|
.into_iter()
|
|
.map(|p| I64Vec2::new(compress_x[&p.x], compress_y[&p.y]))
|
|
.collect();
|
|
self.cache_vertical_lines();
|
|
|
|
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
|
|
.cached_vertical_lines
|
|
.iter()
|
|
.take_while(|line| line.0.x < p.x)
|
|
.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
|
|
})
|
|
.map(|l| l.0.x)
|
|
.chain([p.x])
|
|
.sorted();
|
|
|
|
let line_with = |p: I64Vec2| {
|
|
self.cached_vertical_lines
|
|
.iter()
|
|
.take_while(|line| line.0.x <= p.x)
|
|
.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_with(check) {
|
|
winding = line.winding();
|
|
on_line = true;
|
|
} else {
|
|
on_line = false;
|
|
}
|
|
}
|
|
|
|
winding < 0 || on_line
|
|
}
|
|
|
|
fn cache_vertical_lines(&mut self) {
|
|
self.cached_vertical_lines = self
|
|
.points
|
|
.iter()
|
|
.chain(self.points.first()) // close the loop
|
|
.copied()
|
|
.tuple_windows()
|
|
.map(|(a, b)| Line(a, b))
|
|
.filter(|l| l.is_vertical())
|
|
.sorted_by_key(|line| line.0.x)
|
|
.collect();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
struct Line(I64Vec2, I64Vec2);
|
|
|
|
impl Line {
|
|
/// true if p on line
|
|
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
|
|
}
|
|
}
|
|
|
|
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<I64Vec2> {
|
|
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(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 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<I64Vec2> {
|
|
self.lines()
|
|
.into_iter()
|
|
.flat_map(|l| l.all_points())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
fn uncompress_i64vec2(v: I64Vec2, uncompress: &Uncompress) -> I64Vec2 {
|
|
I64Vec2::new(uncompress.x[&v.x], uncompress.y[&v.y])
|
|
}
|
|
|
|
fn parse_line(line: &str) -> I64Vec2 {
|
|
let (x, y) = line.split_once(',').unwrap();
|
|
I64Vec2::new(x.parse().unwrap(), y.parse().unwrap())
|
|
}
|