diff --git a/Cargo.lock b/Cargo.lock index 1ec1aff..1e0192d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,9 +66,12 @@ dependencies = [ "aoc-runner-derive", "cached", "grid", + "indicatif", "itertools", "misc", + "rayon", "regex", + "rstest", ] [[package]] @@ -92,7 +95,7 @@ dependencies = [ "ahash", "cached_proc_macro", "cached_proc_macro_types", - "hashbrown", + "hashbrown 0.15.5", "once_cell", "thiserror", "web-time", @@ -122,6 +125,44 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "console" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "darling" version = "0.20.11" @@ -163,6 +204,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -181,6 +228,55 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "grid" version = "0.1.0" @@ -196,12 +292,42 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "indicatif" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +dependencies = [ + "console", + "portable-atomic", + "rayon", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "intrusive-collections" version = "0.9.7" @@ -236,6 +362,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + [[package]] name = "memchr" version = "2.7.6" @@ -274,6 +406,33 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -292,6 +451,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.12.2" @@ -321,6 +500,50 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.111", + "unicode-ident", +] + +[[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" @@ -333,6 +556,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -375,6 +604,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "strsim" version = "0.11.1" @@ -423,12 +658,54 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "version_check" version = "0.9.5" @@ -490,6 +767,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.8.31" diff --git a/Cargo.toml b/Cargo.toml index 392985c..dba4132 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,12 @@ aoc-runner = "0.3.0" aoc-runner-derive = "0.3.0" cached = "0.56.0" grid = {version = "0.1.0", path = "utils/grid"} +indicatif = { version = "0.18.3", features = ["rayon"] } itertools = "0.14.0" misc = {path = "utils/misc"} +rayon = "1.11.0" regex = "1.11.1" +rstest = "0.26.1" [profile.release] lto = true diff --git a/src/day9.rs b/src/day9.rs index 20923c5..b818d82 100644 --- a/src/day9.rs +++ b/src/day9.rs @@ -1,11 +1,24 @@ use aoc_runner_derive::{aoc, aoc_generator}; use grid::{AsCoord2d, Coord2d, Grid}; +use indicatif::{ParallelProgressIterator, ProgressIterator, ProgressStyle}; use itertools::Itertools; -use std::cmp::{max, min}; +use rayon::prelude::*; +use std::collections::HashMap; +use std::{ + cmp::{max, min}, + fmt::Display, +}; -#[aoc_generator(day9, part1)] +#[aoc_generator(day9)] fn parse(input: &str) -> Vec { - input.lines().map(|l| l.parse().unwrap()).collect() + 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)] @@ -25,15 +38,13 @@ fn part1(input: &Vec) -> u64 { .unwrap() } -#[aoc_generator(day9, part2)] -fn parse2(input: &str) -> (Vec, Grid) { - let reds = parse(input); - let width = reds.iter().map(|c| c.x()).max().unwrap() + 1; - let height = reds.iter().map(|c| c.y()).max().unwrap() + 1; +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 { + for c in reds { // mark c filled grid.set(&c, b'#'); // build a line of green between it and the previous @@ -52,33 +63,59 @@ fn parse2(input: &str) -> (Vec, Grid) { } prev = c } - println!("grid {}x{}", grid.width(), grid.height()); - (reds, grid) + + 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 1..grid.height() - 1 { - let mut state = match grid.get(&(0, y)) { - Some(b'.') => State::OffLine(false), //noop - Some(b'#') | Some(b'X') => State::OnLine(true), - s => panic!("Unexpected state: {s:?}"), - }; // if the row starts with a ., we start outside - for x in 1..grid.width() - 1 { + 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) | State::OffLine(s) => s, + State::OnLine(s) => s, + State::OffLine(s) => s, }) } None => panic!("overran the grid"), @@ -88,30 +125,219 @@ fn flood_fill(grid: &mut Grid) { } } -#[aoc(day9, part2)] -fn part2((reds, grid): &(Vec, Grid)) -> u64 { - let mut grid = grid.clone(); +// #[aoc(day9, part2, Brute)] +fn part2(reds: &Vec) -> u64 { + let mut grid = build_grid(reds); flood_fill(&mut grid); + println!("{grid}"); - 'outer: for (a, b) in reds + 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() - { - println!( - "{a} vs {b} = {}", - a.x().abs_diff(b.x()) * a.y().abs_diff(b.y()) - ); + .progress() + .collect_vec(); - 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'.' { - continue 'outer; + 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 } - return (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1); + // 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!() } @@ -119,6 +345,7 @@ fn part2((reds, grid): &(Vec, Grid)) -> u64 { #[cfg(test)] mod tests { use super::*; + use rstest::rstest; const EXAMPLE: &str = "7,1 11,1 @@ -136,6 +363,97 @@ mod tests { #[test] fn part2_example() { - assert_eq!(part2(&parse2(EXAMPLE)), 24); + 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); } }