From 479bb0ca91fe63e543c84cdef20dc30abe9cf1cd Mon Sep 17 00:00:00 2001 From: Juris Date: Sat, 21 Dec 2024 06:51:58 +0200 Subject: [PATCH] 2024-20 Cleanup --- rust/Cargo.lock | 28 +++++++-------- rust/common/src/coords2d.rs | 19 ++++++++++ rust/y2024/src/bin/solution_2024_20.rs | 36 +++++++++---------- .../jurisk/adventofcode/y2024/Advent20.scala | 33 ++++++----------- .../scala/jurisk/geometry/Coordinates2D.scala | 12 ++++++- .../adventofcode/y2024/Advent20Spec.scala | 4 +-- 6 files changed, 75 insertions(+), 57 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 241be5cb..fd6dae4d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -222,9 +222,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -234,9 +234,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "shlex", ] @@ -374,9 +374,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "lru" @@ -740,9 +740,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe_arch" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +checksum = "6153d6e19b611afde437944fa67bde0193eb913f63e03b2a7d2ab36be32e42f0" dependencies = [ "bytemuck", ] @@ -863,18 +863,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", @@ -924,9 +924,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wide" -version = "0.7.30" +version = "0.7.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" +checksum = "898b9cda3b6929da16eae368efa7582515168e444b36b6d2c59e1c1297d88184" dependencies = [ "bytemuck", "safe_arch", diff --git a/rust/common/src/coords2d.rs b/rust/common/src/coords2d.rs index 45645181..00fae761 100644 --- a/rust/common/src/coords2d.rs +++ b/rust/common/src/coords2d.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use std::iter::Step; use std::ops::{Add, AddAssign, Neg, Sub}; use std::str::FromStr; @@ -125,6 +126,24 @@ where pub fn manhattan_distance_to_origin(self: Coords2D) -> T { self.x.abs() + self.y.abs() } + + pub fn all_coords_within_manhattan_distance( + self: Coords2D, + distance_inclusive: T, + ) -> Vec> + where + T: Copy + Step, + { + let mut result = Vec::new(); + for x in -distance_inclusive ..= distance_inclusive { + for y in -distance_inclusive ..= distance_inclusive { + if x.abs() + y.abs() <= distance_inclusive { + result.push(self + Coords2D::new(x, y)); + } + } + } + result + } } impl FromStr for Coords2D diff --git a/rust/y2024/src/bin/solution_2024_20.rs b/rust/y2024/src/bin/solution_2024_20.rs index 76e90d8c..d76a869f 100644 --- a/rust/y2024/src/bin/solution_2024_20.rs +++ b/rust/y2024/src/bin/solution_2024_20.rs @@ -10,11 +10,19 @@ type N = i32; type R = usize; type Input = (Coords, MatrixGrid2D, Coords); -#[derive(Eq, PartialEq, Hash, Debug)] +#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)] struct Cheat { - from: Coords, - to: Coords, - distance: N, + from: Coords, + to: Coords, +} + +impl Cheat { + #[must_use] + fn distance(&self) -> N { + let from = self.from; + let to = self.to; + from.manhattan_distance(to) + } } fn parse(input: &str) -> Result { @@ -61,24 +69,17 @@ fn solve(data: &Input, save_at_least: N, max_cheat: N) -> R { .map(|(_, c)| *c) .unwrap_or_default(); let end_cost = from_end.get(&cheat.to).map(|(_, c)| *c).unwrap_or_default(); - start_cost + end_cost + cheat.distance <= goal_cost_threshold + start_cost + end_cost + cheat.distance() <= goal_cost_threshold }; - let empties: Vec<_> = field - .coords() - .filter(|c| field.get(*c).is_some_and(|b| !*b)) - .collect(); + let is_empty = |c: &Coords| -> bool { field.get(*c).is_some_and(|b| !*b) }; let mut valid_cheats = HashSet::new(); - // Could be optimised by avoiding processing all O(N^2), as if we know one coordinate, we can generate all others within maxCheat distance more efficiently - for from in &empties { - for to in &empties { - let from = *from; - let to = *to; - let distance = from.manhattan_distance(to); - if (1 ..= max_cheat).contains(&distance) { - let cheat = Cheat { from, to, distance }; + for c1 in field.coords().filter(is_empty) { + for c2 in c1.all_coords_within_manhattan_distance(max_cheat) { + if is_empty(&c2) && c1 != c2 { + let cheat = Cheat { from: c1, to: c2 }; if valid_cheat(&cheat) { valid_cheats.insert(cheat); } @@ -139,7 +140,6 @@ mod tests { } #[test] - #[ignore] fn test_solve_1_real() { assert_eq!(solve_1(&real_data(), 100), 1293); } diff --git a/scala2/src/main/scala/jurisk/adventofcode/y2024/Advent20.scala b/scala2/src/main/scala/jurisk/adventofcode/y2024/Advent20.scala index a617aa10..b148204f 100644 --- a/scala2/src/main/scala/jurisk/adventofcode/y2024/Advent20.scala +++ b/scala2/src/main/scala/jurisk/adventofcode/y2024/Advent20.scala @@ -65,28 +65,17 @@ object Advent20 { false } - val empties = field.allCoords - .filter(field.at(_).contains(false)) - val cheats = - for { - // Could be optimised by avoiding processing all O(N^2), as if we know one coordinate, we can generate all others within maxCheat distance more efficiently - c1 <- empties - c2 <- empties - if c1.manhattanDistance(c2) <= maxCheat - if c1 != c2 - } yield Cheat(c1, c2) - - println(s"Cheats: ${cheats.size}") - - val selectedCheats = - cheats - .filter(validCheat) - .toSet - - println(s"Selected cheats: ${selectedCheats.size}") - selectedCheats.foreach(println) - - selectedCheats.size + def isEmpty(c: Coords2D): Boolean = + field.at(c).contains(false) + + (for { + c1 <- field.allCoords.filter(isEmpty) + c2 <- c1.allCoordsWithinManhattanDistance(maxCheat) + if isEmpty(c2) + if c1 != c2 + cheat = Cheat(c1, c2) + if validCheat(cheat) + } yield cheat).distinct.size } def part1(data: Input, saveAtLeast: Int): N = diff --git a/scala2/src/main/scala/jurisk/geometry/Coordinates2D.scala b/scala2/src/main/scala/jurisk/geometry/Coordinates2D.scala index 9495e6d1..46aa732a 100644 --- a/scala2/src/main/scala/jurisk/geometry/Coordinates2D.scala +++ b/scala2/src/main/scala/jurisk/geometry/Coordinates2D.scala @@ -1,11 +1,12 @@ package jurisk.geometry -import cats.Functor import cats.implicits._ import jurisk.math.Enumerated +import jurisk.math.Enumerated._ import jurisk.utils.Parsing.StringOps import scala.math.Numeric.Implicits.infixNumericOps +import scala.math.Ordered.orderingToOrdered final case class Coordinates2D[N: Numeric](x: N, y: N) { def +(other: Coordinates2D[N]): Coordinates2D[N] = @@ -25,6 +26,15 @@ final case class Coordinates2D[N: Numeric](x: N, y: N) { ): N = (this - other).manhattanDistanceToOrigin + def allCoordsWithinManhattanDistance( + distanceInclusive: N + )(implicit enumerated: Enumerated[N]): Seq[Coordinates2D[N]] = + for { + x <- -distanceInclusive to distanceInclusive + y <- -distanceInclusive to distanceInclusive + if x.abs + y.abs <= distanceInclusive + } yield this + Coordinates2D.of(x, y) + def adjacent4(implicit integral: Integral[N]): List[Coordinates2D[N]] = neighbours(includeDiagonal = false) def adjacent8(implicit integral: Integral[N]): List[Coordinates2D[N]] = diff --git a/scala2/src/test/scala/jurisk/adventofcode/y2024/Advent20Spec.scala b/scala2/src/test/scala/jurisk/adventofcode/y2024/Advent20Spec.scala index 03ded45a..51ba9434 100644 --- a/scala2/src/test/scala/jurisk/adventofcode/y2024/Advent20Spec.scala +++ b/scala2/src/test/scala/jurisk/adventofcode/y2024/Advent20Spec.scala @@ -29,7 +29,7 @@ class Advent20Spec extends AnyFreeSpec { part1(testData, 2) shouldEqual 1 + 1 + 1 + 1 + 1 + 3 + 2 + 4 + 2 + 14 + 14 } - "real" ignore { + "real" in { part1(realData, 100) shouldEqual 1293 } } @@ -39,7 +39,7 @@ class Advent20Spec extends AnyFreeSpec { part2(testData, 76) shouldEqual 3 } - "real" ignore { + "real" in { part2(realData, 100) shouldEqual 977747 } }