diff --git a/build.sbt b/build.sbt index 6e9092e..7bbb189 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,7 @@ lazy val advent = (project in file(".")) .settings( scalaVersion := "3.3.1", + libraryDependencies += "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % "2.9.0", "org.typelevel" %% "cats-collections-core" % "0.9.5" diff --git a/src/main/scala/org/lemon/advent/year2023/Day05.scala b/src/main/scala/org/lemon/advent/year2023/Day05.scala index 62253ce..42cd3c1 100644 --- a/src/main/scala/org/lemon/advent/year2023/Day05.scala +++ b/src/main/scala/org/lemon/advent/year2023/Day05.scala @@ -1,16 +1,16 @@ package org.lemon.advent.year2023 import scala.collection.immutable.NumericRange +import scala.collection.parallel.CollectionConverters._ private object Day05: - case class RangeMap(destStart: Long, srcRange: NumericRange[Long]) + type Range = NumericRange[Long] - case class Mapping(from: String, to: String, ranges: Seq[(NumericRange[Long], NumericRange[Long])]): - def translate(src: Long) = ranges - .find((s, _) => s.contains(src)) - .map((s, d) => d.start + src - s.start) - .getOrElse(src) + case class RangeMap(src: Range, dest: Range): + def translate(src: Long) = dest.start + src - this.src.start + + case class Mapping(from: String, to: String, ranges: Seq[RangeMap]) def parseMapping(block: String) = val lines = block.split("\n") @@ -21,7 +21,7 @@ private object Day05: .map(_.split(" ")) .map(_.map(x => x.toLong)) .map { case Array(destStart, srcStart, length) => - (srcStart until srcStart + length, destStart until destStart + length) + RangeMap(srcStart until srcStart + length, destStart until destStart + length) } Mapping(from, to, ranges) @@ -34,20 +34,37 @@ private object Day05: (seeds.toSeq, chunks.tail.map(parseMapping)) - def walk(seed: Long, mappings: Iterable[Mapping]) = - mappings.foldLeft(seed)((id, mapping) => mapping.translate(id)) + def walk(seed: Long, mappings: Iterable[Mapping]): (Long, Long) = + def fold(almanacNumAndSafeToSkip: (Long, Long), mapping: Mapping): (Long, Long) = + val (almanacNum, safeToSkip) = almanacNumAndSafeToSkip + mapping.ranges + .find(_.src.contains(almanacNum)) + .map(rng => (rng.translate(almanacNum), math.min(rng.src.end - almanacNum, safeToSkip))) + .getOrElse(( + almanacNum, + mapping.ranges + .map(_.src.start - almanacNum) + .filter(_ > 0) + .minOption.getOrElse(safeToSkip) + )) + + mappings.foldLeft((seed, Long.MaxValue))(fold) + + def run(seeds: Seq[Range], mappings: Iterable[Mapping]) = + def solve(remainingSeeds: Seq[Range], currentSeed: Long, min: Long): Long = + if remainingSeeds.isEmpty then min + else if !remainingSeeds.head.contains(currentSeed) then + if remainingSeeds.tail.isEmpty then min + else solve(remainingSeeds.tail, remainingSeeds.tail.head.start, min) + else + val (loc, safeToSkip) = walk(currentSeed, mappings) + solve(remainingSeeds, currentSeed + safeToSkip, math.min(min, loc)) + solve(seeds, seeds.head.start, Long.MaxValue) def part1(input: String) = val (seeds, mappings) = parse(input) - seeds - .map(walk(_, mappings)) - .min + run(seeds.map(s => s until s + 1), mappings) def part2(input: String) = val (seeds, mappings) = parse(input) - // val min = seeds - // .grouped(2) - // .flatMap { case Seq(start, length) => (start until start + length) } - // .minBy(walk(_, mappings)) - // walk(min, mappings) - 0 + run(seeds.grouped(2).map { case Seq(start, length) => (start until start + length) }.toSeq, mappings) diff --git a/src/test/scala/org/lemon/advent/year2023/Day05Test.scala b/src/test/scala/org/lemon/advent/year2023/Day05Test.scala index 2c2fe86..c21414f 100644 --- a/src/test/scala/org/lemon/advent/year2023/Day05Test.scala +++ b/src/test/scala/org/lemon/advent/year2023/Day05Test.scala @@ -48,9 +48,9 @@ class Day05Test extends UnitTest: } test("part 2 example") { - part2(in) shouldBe 0 + part2(in) shouldBe 46 } test("part 2") { - part2(read(file(2023)(5))) shouldBe 0 + part2(read(file(2023)(5))) shouldBe 27992443 }