diff --git a/2024/src/main/scala/aoc2024/Day10.scala b/2024/src/main/scala/aoc2024/Day10.scala index 6d3067b..cf94cc6 100644 --- a/2024/src/main/scala/aoc2024/Day10.scala +++ b/2024/src/main/scala/aoc2024/Day10.scala @@ -3,22 +3,25 @@ package aoc2024 import nmcb.* import nmcb.pos.* +import scala.annotation.tailrec + object Day10 extends AoC: val grid: Grid[Int] = Grid.fromLines(lines).map(_.asDigit) - val heads: Set[Pos] = - grid.filter((_, i) => i == 0).map(_.pos) - type TrailHead = Set[Vector[Pos]] extension (trails: TrailHead) + def reachableSummits: Iterable[Int] = trails.groupMap(_.head)(_.last).values.map(_.size) extension (g: Grid[Int]) + def startingPoints: Set[Pos] = + g.filter((_, i) => i == 0).map(_.pos) + def trailsFrom(head: Pos): TrailHead = def step(trail: Vector[Pos]): TrailHead = @@ -26,18 +29,19 @@ object Day10 extends AoC: .filter(n => g.within(n) && g.peek(n) == g.peek(trail.last) + 1) .map(p => trail :+ p) + @tailrec def loop(trails: TrailHead, current: Int): TrailHead = val result = trails.filter(trail => g.peek(trail.last) == current) if current >= 9 then result else loop(result.flatMap(step), current + 1) loop(Set(Vector(head)), 0) - def score: Long = - heads.flatMap(grid.trailsFrom).reachableSummits.sum + def score(grid: Grid[Int]): Long = + grid.startingPoints.flatMap(grid.trailsFrom).reachableSummits.sum - def rating: Long = - heads.flatMap(grid.trailsFrom).size + def rating(grid: Grid[Int]): Long = + grid.startingPoints.flatMap(grid.trailsFrom).size - override lazy val answer1: Long = grid.score - override lazy val answer2: Long = grid.rating + override lazy val answer1: Long = score(grid) + override lazy val answer2: Long = rating(grid) diff --git a/2024/src/main/scala/aoc2024/Day11.scala b/2024/src/main/scala/aoc2024/Day11.scala index 96b2acb..b24856d 100644 --- a/2024/src/main/scala/aoc2024/Day11.scala +++ b/2024/src/main/scala/aoc2024/Day11.scala @@ -7,22 +7,24 @@ object Day11 extends AoC: type Stone = String - extension (s: Stone) def dropLeadingZeros: Stone = - val dropped = s.dropWhile(_ == '0') - if dropped.isEmpty then "0" else dropped + extension (s: Stone) - type StoneCount = (stone: Stone, count: Long) + def dropLeadingZeros: Stone = + val dropped = s.dropWhile(_ == '0') + if dropped.isEmpty then "0" else dropped - def update(ss: StoneCount): Vector[StoneCount] = - val handle = ss.stone match - case "0" => - Vector("1") - case s if s.length % 2 == 0 => - val (l, r) = s.splitAt(s.length / 2) - Vector(l.dropLeadingZeros, r.dropLeadingZeros) - case n => - Vector((n.toLong * 2024).toString) + type StoneCount = (stone: Stone, count: Long) + def update(ss: StoneCount): Vector[StoneCount] = + val handle: Vector[String] = + ss.stone match + case "0" => + Vector("1") + case s if s.length % 2 == 0 => + val (l, r) = s.splitAt(s.length / 2) + Vector(l.dropLeadingZeros, r.dropLeadingZeros) + case n => + Vector((n.toLong * 2024).toString) handle.map(_ -> ss.count) :+ (ss.stone -> -ss.count) val stones: Vector[StoneCount] = input.split(' ').toVector.map(_ -> 1L) diff --git a/2024/src/main/scala/aoc2024/Day12.scala b/2024/src/main/scala/aoc2024/Day12.scala index 2c31d6b..d439577 100644 --- a/2024/src/main/scala/aoc2024/Day12.scala +++ b/2024/src/main/scala/aoc2024/Day12.scala @@ -9,7 +9,7 @@ import scala.annotation.* object Day12 extends AoC: type Tree = Char - type Fence = (Pos,Dir) + type Fence = (Pos, Dir) type Fences = Set[Fence] extension (f: Fence) @@ -30,7 +30,6 @@ object Day12 extends AoC: object Fences: - def around(pos: Pos): Fences = Set((pos, N), (pos, E), (pos, S), (pos, W)) @@ -46,14 +45,14 @@ object Day12 extends AoC: def countSides(positions: Set[Int]): Int = @tailrec - def loop(l: List[Int], last: Option[Int] = None, result: Int = 0): Int = - l match - case Nil => result - case h :: t if last.contains(h - 1) => loop(t, Some(h), result) - case h :: t => loop(t, Some(h), result + 1) - loop(positions.toList.sorted) - - def fencePositions(d: Dir, p: Map[Dir,Set[Pos]], g: Pos => Int, f: Pos => Int): Map[Int,Set[Int]] = + def loop(l: Vector[Int], last: Option[Int] = None, result: Int = 0): Int = + l.runtimeChecked match + case Vector() => result + case h +: t if last.contains(h - 1) => loop(t, Some(h), result) + case h +: t => loop(t, Some(h), result + 1) + loop(positions.toVector.sorted) + + def fencePositions(d: Dir, p: Map[Dir, Set[Pos]], g: Pos => Int, f: Pos => Int): Map[Int, Set[Int]] = p.get(d).map(_.groupMap(g)(f)).getOrElse(sys.error(s"no fence: $d")) val fence = fences.groupMap(_.right)(_.left) @@ -80,10 +79,11 @@ object Day12 extends AoC: plots.contains(p) - extension (g: Grid[Tree]) + extension (trees: Grid[Tree]) def regionOf(pos: Pos): Region = - val tree = g.peek(pos) + val tree = trees.peek(pos) + @tailrec def loop(todo: Set[Pos], region: Region): Region = if todo.isEmpty then @@ -91,29 +91,30 @@ object Day12 extends AoC: else val pos = todo.head val rest = todo.tail - if !region.contains(pos) && g.contains(pos, tree) then - loop(rest ++ pos.adjWithinGrid(g, (p,_) => !region.contains(p)), region.add(pos)) + if !region.contains(pos) && trees.contains(pos, tree) then + loop(rest ++ pos.adjWithinGrid(trees, (p, _) => !region.contains(p)), region.add(pos)) else loop(rest, region) - val r = Region(tree, Set(pos), fences = Fences.around(pos)) - loop(pos.adjWithinGrid(g, _ => true), r) + val region = Region(tree, Set(pos), fences = Fences.around(pos)) + loop(pos.adjWithinGrid(trees, _ => true), region) def regions: Vector[Region] = + @tailrec def loop(todo: Set[Pos], visited: Set[Pos] = Set.empty, result: Vector[Region] = Vector.empty): Vector[Region] = if todo.isEmpty then result else - val p = todo.head - val t = todo.tail - if visited.contains(p) then - loop(t, visited, result) + val pos = todo.head + val rest = todo.tail + if visited.contains(pos) then + loop(rest, visited, result) else - val r = g.regionOf(p) - loop(t, visited = visited ++ r.plots, result :+ r) + val region = trees.regionOf(pos) + loop(rest, visited = visited ++ region.plots, result :+ region) - loop(g.positions) + loop(trees.positions) val garden: Grid[Tree] = Grid.fromLines(lines) diff --git a/2024/src/main/scala/aoc2024/Day13.scala b/2024/src/main/scala/aoc2024/Day13.scala index 37d3322..51a27e9 100644 --- a/2024/src/main/scala/aoc2024/Day13.scala +++ b/2024/src/main/scala/aoc2024/Day13.scala @@ -29,7 +29,7 @@ object Day13 extends AoC: val ps = lines.collect: case s"Prize: X=$x, Y=$y" => Position(x.toLong, y.toLong) as.zip(bs).zip(ps).map: - case ((a,b),p) => Machine(a, b, p) + case ((a, b), p) => Machine(a, b, p) override lazy val answer1: Long = machines.flatMap(_.solve(offset = 0L)).sum - override lazy val answer2: Long = machines.flatMap(_.solve(offset = 10000000000000L)).sum + override lazy val answer2: Long = machines.flatMap(_.solve(offset = 10_000_000_000_000L)).sum