use aoc_runner_derive::{aoc, aoc_generator}; use grid::{AsCoord2d, Coord2d, Grid}; use indicatif::{ParallelProgressIterator, ProgressIterator, ProgressStyle}; use itertools::Itertools; use rayon::prelude::*; use std::collections::HashMap; use std::{ cmp::{max, min}, fmt::Display, }; #[aoc_generator(day9)] fn parse(input: &str) -> Vec { input .lines() .map(|l| l.parse::().unwrap()) .map(|c| Coord2d { x: c.x + 1, // allow a buffer zone y: c.y + 1, }) .collect() } #[aoc(day9, part1)] fn part1(input: &Vec) -> u64 { input .iter() .tuple_combinations() .inspect(|(a, b)| { println!( "{a} vs {b} = {}", a.x().abs_diff(b.x()) * a.y().abs_diff(b.y()) ) }) .map(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)) .sorted_unstable() .next_back() .unwrap() } fn build_grid(reds: &Vec) -> Grid { let width = reds.iter().map(|c| c.x()).max().unwrap() + 3; let height = reds.iter().map(|c| c.y()).max().unwrap() + 3; let mut grid = Grid::with_shape(width as usize, height as usize, b'.'); let mut prev = reds.last().unwrap(); for c in reds { // mark c filled grid.set(&c, b'#'); // build a line of green between it and the previous if c.x() == prev.x() { // vertical for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) { grid.set(&(c.x(), y), b'X'); } } else if c.y() == prev.y() { // horiztonal for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) { grid.set(&(x, c.y()), b'X'); } } else { panic!() } prev = c } grid } fn build_hashgrid(reds: &Vec) -> HashMap { let mut grid = HashMap::new(); let mut prev = reds.last().unwrap(); for c in reds { // mark c filled grid.insert(*c, b'#'); // build a line of green between it and the previous if c.x() == prev.x() { // vertical for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) { grid.insert(Coord2d { x: c.x(), y }, b'X'); } } else if c.y() == prev.y() { // horiztonal for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) { grid.insert(Coord2d { x, y: c.y() }, b'X'); } } else { panic!() } prev = c } grid } // FIXME: TOTALLY BROKEN fn flood_fill(grid: &mut Grid) { #[derive(Debug, Eq, PartialEq)] enum State { OffLine(bool), // Off a line(true=inside) OnLine(bool), // On a line(previous state) } for y in 0..grid.height() { let mut state = State::OffLine(false); for x in 0..grid.width() { match grid.get(&(x, y)) { Some(b'.') => { if state == State::OffLine(true) || state == State::OnLine(false) { grid.set(&(x, y), b'X'); state = State::OffLine(true) } else { state = State::OffLine(false) } } Some(b'#') | Some(b'X') => { state = State::OnLine(match state { State::OnLine(s) => s, State::OffLine(s) => s, }) } None => panic!("overran the grid"), Some(c) => panic!("unexpected value {c}"), } } } } // #[aoc(day9, part2, Brute)] fn part2(reds: &Vec) -> u64 { let mut grid = build_grid(reds); flood_fill(&mut grid); println!("{grid}"); let pairs = reds .iter() .tuple_combinations() .sorted_unstable_by_key(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)) .rev() .progress() .collect_vec(); let (a, b) = pairs .par_iter() .progress_with_style( ProgressStyle::with_template( "[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}", ) .unwrap(), ) .filter(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1) < 2972065369) // FIXME: PROGRESS CAPTURE .map(|(a, b)| { for y in (min(a.y(), b.y()))..=max(a.y(), b.y()) { for x in (min(a.x(), b.x()))..=max(a.x(), b.y()) { if *grid.get(&(x, y)).unwrap() == b'.' { return (false, a, b); } } } (true, a, b) }) .find_map_first(|(good, a, b)| if good { Some((a, b)) } else { None }) .unwrap(); println!("win: {a} {b}"); (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1) } fn line_square_intersection( &(l1, l2): &(<, <), &(sq1, sq2): &(&ST, &ST), ) -> bool where LT: Clone + Display, ST: Clone + Display, { let min_x = min(sq1.x(), sq2.x()); let max_x = max(sq1.x(), sq2.x()); let min_y = min(sq1.y(), sq2.y()); let max_y = max(sq1.y(), sq2.y()); // println!("({},{}) X [{},{}]", l1, l2, sq1, sq2); // line is horizontal if l1.y() == l2.y() { // println!(" horizontal at y={}", l1.y()); // above, below, or touching square if l1.y() <= min_y || l1.y() >= max_y { // println!(" y out of range or touching"); false // start inside } else if min(l1.x(), l2.x()) >= min_x && min(l1.x(), l2.x()) <= max_x { // println!(" start inside"); true //end inside } else if max(l1.x(), l2.x()) >= min_x && max(l1.x(), l2.x()) <= max_x { // println!(" end inside"); true } else { // println!(" no overlap on x"); false } // line is vertical } else { // println!(" vertical at x={}", l1.x()); if l1.x() <= min_x || l1.x() >= max_x { // println!(" x out of range or touching"); false // start inside } else if min(l1.y(), l2.y()) >= min_y && min(l1.y(), l2.y()) <= max_y { // println!(" start inside"); true //end inside } else if max(l1.y(), l2.y()) >= min_y && max(l1.y(), l2.y()) <= max_y { // println!(" end inside"); true } else { // println!(" no overlap on y"); false } } } trait Rectangle { fn top_left(&self) -> Coord2d; fn bottom_right(&self) -> Coord2d; fn in_bounds(&self, b: &T) -> bool { b.x() >= self.top_left().x && b.x() <= self.bottom_right().x() && b.y() >= self.top_left().y() && b.y() <= self.bottom_right().y() } fn area(&self) -> u64 { let a = self.top_left(); let b = self.bottom_right(); (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1) } } impl Rectangle for (&Coord2d, &Coord2d) { fn top_left(&self) -> Coord2d { Coord2d { x: min(self.0.x, self.1.x), y: min(self.0.y, self.1.y), } } fn bottom_right(&self) -> Coord2d { Coord2d { x: max(self.0.x, self.1.x), y: max(self.0.y, self.1.y), } } } // true = clockwise fn outside_points(prev: &Coord2d, cur: &Coord2d, next: &Coord2d) -> Vec { let l1_diff = ((cur.x - prev.x).signum(), (cur.y - prev.y).signum()); let l2_diff = ((next.x - cur.x).signum(), (next.y - cur.y).signum()); match (l1_diff, l2_diff) { // CW ((1, 0), (0, 1)) => vec![cur + (0, -1), cur + (1, -1), cur + (1, 0)], // x^ y^ ((0, 1), (-1, 0)) => vec![cur + (1, 0), cur + (1, 1), cur + (0, -1)], // y^ xv ((-1, 0), (0, -1)) => vec![cur + (0, 1), cur + (-1, 1), cur + (-1, 0)], // xv yv ((0, -1), (1, 0)) => vec![cur + (-1, 0), cur + (-1, -1), cur + (0, -1)], // yv x^ // CCW ((0, 1), (1, 0)) => vec![cur + (1, -1)], ((1, 0), (0, -1)) => vec![cur + (-1, -1)], ((0, -1), (-1, 0)) => vec![cur + (-1, 1)], ((-1, 0), (0, 1)) => vec![cur + (1, 1)], ((0, _), (0, _)) | ((_, 0), (_, 0)) => vec![], // colinear _ => panic!( "unexpected line arrangement {:?} {:?} @ [({},{}), ({},{}), ({},{})", l1_diff, l2_diff, prev.x, prev.y, cur.x, cur.y, next.x, next.y ), } } #[aoc(day9, part2, Lines)] fn part2_lines(reds: &Vec) -> u64 { // let mut grid = build_hashgrid(reds); let mut grid = build_grid(reds); println!("{grid}"); let outside_points: HashMap<&Coord2d, Vec> = reds .iter() .circular_tuple_windows() .map(|(a, b, c)| (b, outside_points(a, b, c))) .collect(); // for corner in reds { // for p in &outside_points[corner] { // if *grid.get(p).unwrap() == b'.' { // grid.set(p, b'O'); // } // } // } // println!("{grid}"); // still iterate over the possible rectangles in size order for rect in reds .iter() .tuple_combinations() .sorted_unstable_by_key(|rect: &(&Coord2d, &Coord2d)| rect.area()) .rev() .progress_with_style( ProgressStyle::with_template( "[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}", ) .unwrap(), ) { println!( "[{},{}] = {}", rect.top_left(), rect.bottom_right(), rect.area() ); // then for each corner, check if any of its outside edges are inside and not part of another line if reds .iter() .filter(|corner| rect.in_bounds(corner)) .inspect(|c| println!(" corner: {c}")) .any(|corner| { outside_points[corner] .iter() .inspect(|p| println!(" {} = {}", p, *grid.get(p).unwrap() as char)) .any(|p| rect.in_bounds(p) && *grid.get(p).unwrap() == b'.') }) { continue; } println!( "win! {},{} = {}", rect.top_left(), rect.bottom_right(), rect.area() ); grid.set(&rect.top_left(), b'*'); grid.set(&rect.bottom_right(), b'*'); println!("{grid}"); return rect.area(); } panic!() } #[cfg(test)] mod tests { use super::*; use rstest::rstest; const EXAMPLE: &str = "7,1 11,1 11,7 9,7 9,5 2,5 2,3 7,3"; #[test] fn part1_example() { assert_eq!(part1(&parse(EXAMPLE)), 50); } #[test] fn part2_example() { assert_eq!(part2(&parse(EXAMPLE)), 24); } #[rstest] #[case( "7,1 11,1 11,7 9,7 9,5 2,5 2,3 7,3", 24 )] #[case( "4,2 13,2 13,4 8,4 8,6 11,6 11,10 4,10", 40 )] #[case( "3,2 17,2 17,13 13,13 13,11 15,11 15,8 11,8 11,15 18,15 18,17 4,17 4,12 6,12 6,5 3,5", 66 )] #[case( "2,2 8,2 8,6 5,6 5,4 2,4", 21 )] #[case( "3,1 12,1 12,4 9,4 9,8 3,8", 56 )] #[case( "7,1 11,1 11,3 13,3 13,5 11,5 11,7 9,7 9,5 2,5 2,3 7,3", 36 )] #[case( "1,2 8,2 8,4 6,4 6,5 9,5 9,8 1,8", 56 )] fn part2_lines_example(#[case] input: &str, #[case] answer: u64) { assert_eq!(part2_lines(&parse(input)), answer); } }