From af8413cba8a12679521dbf4767586cf900ef380f Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 28 Dec 2023 13:47:30 +0100 Subject: [PATCH] Fix Day 21 --- Cargo.lock | 60 +++++++++++ Cargo.toml | 1 + flake.nix | 13 ++- src/bin/day21.rs | 253 ++++++++++++++++++++++++++--------------------- 4 files changed, 214 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f0a41a..6d312c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,7 @@ dependencies = [ "itertools", "ndarray", "num", + "rand", "regex", ] @@ -72,6 +73,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -91,6 +103,12 @@ dependencies = [ "either", ] +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + [[package]] name = "matrixmultiply" version = "0.3.8" @@ -202,6 +220,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -220,6 +244,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -278,6 +332,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "zerocopy" version = "0.7.31" diff --git a/Cargo.toml b/Cargo.toml index 8c1e263..e3d86c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ hashbrown = "0.14.3" itertools = "0.12.0" ndarray = "0.15.6" num = "0.4.1" +rand = "0.8.5" regex = "1.10.2" diff --git a/flake.nix b/flake.nix index 036871d..1f39900 100644 --- a/flake.nix +++ b/flake.nix @@ -19,11 +19,18 @@ devShells = forAllSystems (pkgs: with pkgs; { default = mkShell { - nativeBuildInputs = [ rustc cargo ]; - buildInputs = [ hyperfine ]; + nativeBuildInputs = [ rustc cargo clippy ]; + buildInputs = [ hyperfine glow libiconv ]; RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; + shellHook = '' +bench() { + cargo build --release + DAY=$((($(date +%s)-$(date +%s --date "2023-11-30"))/(3600*24))) + hyperfine --parameter-list day $(seq --format "%02.f" 1 $DAY | paste -sd ',' -) "./target/release/day{day} ./data/{day}.in" --runs 20 --warmup 10 --shell=none --export-markdown ./target/bench-output.md 2> /dev/null + glow ./target/bench-output.md +} +''; }; }); - }; } diff --git a/src/bin/day21.rs b/src/bin/day21.rs index 8c47cd7..788cbdc 100644 --- a/src/bin/day21.rs +++ b/src/bin/day21.rs @@ -1,11 +1,88 @@ use anyhow::{Context, Result}; -fn bfs(grid: &Vec, height: usize, width: usize, start: (i64, i64)) -> Vec { - let mut distances = vec![u64::MAX; grid.len()]; - distances[start.0 as usize * width + start.1 as usize] = 0; +const STEPS_P1: u64 = 64; +const STEPS_P2: usize = 202300 * 131 + 65; - let height = height as i64; - let width = width as i64; +struct Plot { + start: (usize, usize), + height: usize, + width: usize, + scale: usize, + grid: Vec, +} + +impl Plot { + #[rustfmt::skip] + fn new(grid: Vec, width: usize) -> Self { + let height = grid.len() / width; + let mut start = (height / 2, width / 2); + for x in 0..height { + for y in 0..width { + if grid[x * width + y] == b'S' { + start = (x, y); + } + } + } + + Self { start, height, width, scale: 0, grid } + } + + fn scale_up(&mut self) { + self.scale_to(self.scale + 1); + } + + fn scale_to(&mut self, new_scale: usize) { + let old_factor = 2 * self.scale + 1; + let factor = 2 * new_scale + 1; + let height = self.height; + let width = self.width; + self.grid.resize(factor * factor * height * width, 0); + + for x in (0..height).rev() { + for y in 0..width { + self.grid[x * width * factor + y] = self.grid[x * old_factor * width + y]; + } + } + + for xf in 0..factor { + for x in 0..height { + let row = x * width * factor + xf * factor * height * width; + for yf in 0..factor { + let col_start = row + yf * width; + for y in 0..width { + self.grid[col_start + y] = self.grid[x * width * factor + y]; + } + } + } + } + + self.scale = new_scale; + } + + fn scale_height(&self) -> usize { + (2 * self.scale + 1) * self.height + } + + fn scale_width(&self) -> usize { + (2 * self.scale + 1) * self.width + } + + fn scale_start(&self) -> (usize, usize) { + ( + self.start.0 + self.scale * self.height, + self.start.1 + self.scale * self.width, + ) + } +} + +fn bfs(plot: &Plot) -> Vec { + let mut distances = vec![u64::MAX; plot.grid.len()]; + let start = plot.scale_start(); + distances[start.0 * plot.scale_width() + start.1] = 0; + + let height = plot.scale_height() as i32; + let width = plot.scale_width() as i32; + let start = (start.0 as i32, start.1 as i32); let mut todo = Vec::from([start]); let mut next = Vec::new(); @@ -16,7 +93,7 @@ fn bfs(grid: &Vec, height: usize, width: usize, start: (i64, i64)) -> Vec, height: usize, width: usize, start: (i64, i64)) -> Vec, height: usize, width: usize) -> Vec { - let mut result = vec![0; height * width * factor * factor]; +fn interpolate(x0: usize, mut ys: [i64; 4], x: usize) -> Option { + assert!(x >= x0); + if x <= x0 + 3 { + Some(ys[(x - x0) as usize]) + } else { + let mut f = [0; 4]; - for xf in 0..factor { - for yf in 0..factor { - let base = xf * height * width * factor + yf * width; - for x in 0..height { - for y in 0..width { - result[base + x * width * factor + y] = grid[x * width + y]; - } + for i in 0..4 { + f[i] = ys[0]; + for i in 0..ys.len() - 1 { + ys[i] = ys[i + 1] - ys[i]; } } + + let xa = (x - x0) as i64; + let xb = xa * (x - x0 - 1) as i64; + (f[3] == 0).then_some(f[0] + f[1] * xa as i64 + f[2] * xb / 2) } - return result; } fn main() -> Result<()> { @@ -55,103 +136,55 @@ fn main() -> Result<()> { let input = std::fs::read_to_string(filename)?; let width = input.lines().next().unwrap().len(); let grid: Vec<_> = input.lines().flat_map(|line| line.bytes()).collect(); - let height = grid.len() / width; - let mut start1 = (0, 0); - for x in 0..height { - for y in 0..width { - if grid[x * width + y] == b'S' { - start1 = (x as i64, y as i64); + let mut plot = Plot::new(grid, width); + + let positions = |distances: &Vec, step_count: u64| -> u64 { + let mut positions = 0; + for distance in distances.iter().copied() { + positions += ((distance <= step_count) & (distance % 2 == step_count % 2)) as u64 + } + positions + }; + + let distances = bfs(&plot); + let part1 = positions(&distances, STEPS_P1); + + assert!(plot.height == plot.width); + let side_length = width; + let required_scale = + |step_count| (step_count - plot.start.0 as usize + side_length) / side_length; + + let offset = STEPS_P2 % side_length; + let max_scale = required_scale(STEPS_P2); + + let part2 = if max_scale < 4 { + plot.scale_to(max_scale); + let distances = bfs(&plot); + positions(&distances, STEPS_P2 as u64) + } else { + let scale0 = required_scale(offset); + let mut sequence = [0; 4]; + + plot.scale_to(scale0 + 3); + let distances = bfs(&plot); + for i in 0..4 { + sequence[i] = positions(&distances, (offset + i * side_length) as u64) as i64; + } + + let mut n = 0; + loop { + if let Some(v) = interpolate(n, sequence, max_scale - scale0) { + break v as u64; } + + plot.scale_up(); + let distances = bfs(&plot); + sequence.rotate_left(1); + n += 1; + sequence[3] = positions(&distances, (offset + (n + 3) * side_length) as u64) as i64; } - } - - let distances = bfs(&grid, height, width, start1); - - let steps_p1 = 64; - let mut part1 = 0; - for x in 0..height { - for y in 0..width { - let distance = distances[x * width + y]; - if distance <= steps_p1 && distance % 2 == steps_p1 % 2 { - part1 += 1; - } - } - } - - let center = (height as i64 / 2, width as i64 / 2); - let scale = 2usize; - let repeats = scale * 2 + 1; - let scale_center = ( - center.0 + (scale * height) as i64, - center.1 + (scale * width) as i64, - ); - let scale_grid = repeat_grid(repeats, &grid, height, width); - - let distances = bfs(&scale_grid, height * repeats, width * repeats, scale_center); - - let mut counts = vec![vec![0; repeats]; repeats]; - let limit = (scale * height + height / 2) as u64; - for qx in 0..repeats { - for qy in 0..repeats { - let base = qx * height * width * repeats + qy * width; - for x in 0..height { - for y in 0..width { - let distance = distances[base + x * width * repeats + y]; - if distance <= limit as u64 && limit as u64 % 2 == distance % 2 { - counts[qx][qy] += 1; - } - } - } - } - } - - let part_2_steps = 26501365; - let part_2_scale: u64 = ((part_2_steps - height / 2) / height) as u64; - - let mut part2 = 0; - - for x in 0..repeats { - for y in 0..repeats { - print!("{: >6} ", counts[x][y]); - } - - println!(); - } - - // ..*#*.. - // .*'o'*. - // *'o+o'* - // #o+o+o# - // *'o+o'* - // .*'o'*. - // ..*#*.. - - // o Even centers - let inner_even_count = ((part_2_scale - 1) | 0x1) * ((part_2_scale - 1) | 0x1); - part2 += inner_even_count * counts[scale][scale]; - - let inner_odd_count = (part_2_scale & (!0x1)) * (part_2_scale & (!0x1)); - // + Odd centers - part2 += inner_odd_count * counts[scale][scale + 1]; - - // # Pointy ends - part2 += counts[0][scale]; - part2 += counts[repeats - 1][scale]; - part2 += counts[scale][0]; - part2 += counts[scale][repeats - 1]; - - // * Outer edges - part2 += part_2_scale * counts[0][scale - 1]; - part2 += part_2_scale * counts[0][scale + 1]; - part2 += part_2_scale * counts[repeats - 1][scale - 1]; - part2 += part_2_scale * counts[repeats - 1][scale + 1]; - - // ' Inner edges - part2 += (part_2_scale - 1) * counts[1][scale - 1]; - part2 += (part_2_scale - 1) * counts[1][scale + 1]; - part2 += (part_2_scale - 1) * counts[repeats - 2][scale - 1]; - part2 += (part_2_scale - 1) * counts[repeats - 2][scale + 1]; + }; println!("1) {}", part1); println!("2) {}", part2);