diff --git a/Cargo.lock b/Cargo.lock index c032e7c..bb03321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,7 @@ version = "0.0.0" dependencies = [ "anyhow", "cached", + "derive_more", "glam", "itertools", ] @@ -116,6 +117,27 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -206,12 +228,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 3aa0d95..234fed2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" [dependencies] anyhow = "1.0.100" cached = "0.56.0" +derive_more = { version = "2.1.0", features = ["deref"] } glam = "0.30.9" itertools = "0.14.0" diff --git a/src/bin/08.rs b/src/bin/08.rs index 37191c0..14c24ad 100644 --- a/src/bin/08.rs +++ b/src/bin/08.rs @@ -46,8 +46,8 @@ fn part2(input: &str) -> usize { } fn connect(mut circuits: Vec, (a, b): (JBox, JBox)) -> Vec { - let with_a = circuits.iter_mut().position(|c| c.contains(&a)); - let with_b = circuits.iter_mut().position(|c| c.contains(&b)); + let with_a = circuits.iter().position(|c| c.contains(&a)); + let with_b = circuits.iter().position(|c| c.contains(&b)); match (with_a, with_b) { (Some(with_a), Some(with_b)) => { // Merge into with_a. with_b will be empty, but whatever. @@ -71,8 +71,7 @@ fn pairs(boxes: &[JBox]) -> impl Iterator + use<'_> { boxes .iter() .copied() - .combinations(2) - .map(|v| (v[0], v[1])) + .tuple_combinations() .sorted_by_key(|&(a, b)| distance(a, b)) } diff --git a/src/bin/09.rs b/src/bin/09.rs new file mode 100644 index 0000000..83eafbf --- /dev/null +++ b/src/bin/09.rs @@ -0,0 +1,132 @@ +use aoc::*; +use derive_more::Deref; +use glam::U64Vec2; +use itertools::Itertools; +use std::ops::Sub; + +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(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_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); + polygon + .iter() + .copied() + .tuple_combinations() + .map(Rect::from) + .filter(|&r| polygon.contains_rect(r)) + .map(Rect::area) + .max() + .unwrap() +} + +#[derive(Deref)] +struct Polygon(Vec); + +impl Polygon { + fn parse(input: &str) -> Self { + Self(input.lines().map(parse_line).collect()) + } + + fn contains_rect(&self, r: Rect) -> bool { + r.all_points().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)); + + let mut inside = false; + let mut was_on_line = false; + + 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; + } + was_on_line = is_on_line; + } + + inside || was_on_line + } + + fn lines(&self) -> Vec { + self.iter() + .chain(self.first()) // close the loop + .copied() + .tuple_combinations() + .map(|(a, b)| Line(a, b)) + .collect() + } +} + +#[derive(Clone, Copy)] +struct Line(U64Vec2, U64Vec2); + +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 + } +} + +#[derive(Copy, Clone)] +struct Rect(U64Vec2, U64Vec2); + +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 { + Self(a, b) + } + + 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 + } +} + +fn parse_line(line: &str) -> U64Vec2 { + let (x, y) = line.split_once(',').unwrap(); + U64Vec2::new(x.parse().unwrap(), y.parse().unwrap()) +}