Skip to content

Commit

Permalink
Solve 2024 day 20 part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
sim642 committed Dec 20, 2024
1 parent 44acb72 commit eedd1fe
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 28 deletions.
110 changes: 84 additions & 26 deletions src/main/scala/eu/sim642/adventofcode2024/Day20.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,97 @@ object Day20 {
}
}

def findCheats(grid: Grid[Char]): Set[Cheat] = {
val forwardSearch = gridGraphSearch(grid, 'S', 'E')
val forwardResult = BFS.search(forwardSearch)
val backwardSearch = gridGraphSearch(grid, 'E', 'S')
val backwardResult = BFS.search(backwardSearch)

val noCheatDistance = forwardResult.target.get._2

(for {
(row, y) <- grid.view.zipWithIndex
(cell, x) <- row.view.zipWithIndex
if cell == '#'
pos = Pos(x, y)
startOffset <- Pos.axisOffsets
start = pos + startOffset
if grid.containsPos(start) && grid(start) != '#'
endOffset <- Pos.axisOffsets
if startOffset != endOffset
end = pos + endOffset
if grid.containsPos(end) && grid(end) != '#'
cheatDistance = forwardResult.distances(start) + 2 + backwardResult.distances(end)
//if cheatDistance <= noCheatDistance
save = noCheatDistance - cheatDistance
} yield Cheat(start, end, save)).toSet
trait Part {
def findCheats(grid: Grid[Char]): Set[Cheat]

def countGoodCheats(grid: Grid[Char]): Int = findCheats(grid).count(_.save >= 100)
}

object Part1 extends Part {
override def findCheats(grid: Grid[Char]): Set[Cheat] = {
val forwardSearch = gridGraphSearch(grid, 'S', 'E')
val forwardResult = BFS.search(forwardSearch)
val backwardSearch = gridGraphSearch(grid, 'E', 'S')
val backwardResult = BFS.search(backwardSearch)

val noCheatDistance = forwardResult.target.get._2

(for {
(row, y) <- grid.view.zipWithIndex
(cell, x) <- row.view.zipWithIndex
if cell == '#'
pos = Pos(x, y)
startOffset <- Pos.axisOffsets
start = pos + startOffset
if grid.containsPos(start) && grid(start) != '#'
endOffset <- Pos.axisOffsets
if startOffset != endOffset
end = pos + endOffset
if grid.containsPos(end) && grid(end) != '#'
cheatDistance = forwardResult.distances(start) + 2 + backwardResult.distances(end)
//if cheatDistance <= noCheatDistance
save = noCheatDistance - cheatDistance
} yield Cheat(start, end, save)).toSet
}
}

def countGoodCheats(grid: Grid[Char]): Int = findCheats(grid).count(_.save >= 100)
object Part2 extends Part {
override def findCheats(grid: Grid[Char]): Set[Cheat] = {
val forwardSearch = gridGraphSearch(grid, 'S', 'E')
val forwardResult = BFS.search(forwardSearch)
val backwardSearch = gridGraphSearch(grid, 'E', 'S')
val backwardResult = BFS.search(backwardSearch)

val noCheatDistance = forwardResult.target.get._2

// TODO: optimize

/*(for {
(row, y) <- grid.view.zipWithIndex
(cell, x) <- row.view.zipWithIndex
if cell == '.'
start = Pos(x, y)
startOffset <- Pos.axisOffsets
startCheat <- 0 to 20
pos = start + startCheat *: startOffset
if grid.containsPos(pos)
endOffset <- Pos.axisOffsets
if startOffset != endOffset && startOffset != -endOffset
endCheat <- 0 to (20 - startCheat)
end = pos + endCheat *: endOffset
if grid.containsPos(end) && grid(end) != '#'
cheatDistance = forwardResult.distances(start) + (startCheat + endCheat) + backwardResult.distances(end)
//if cheatDistance <= noCheatDistance
save = noCheatDistance - cheatDistance
} yield Cheat(start, end, save)).toSet*/

(for {
(row, y) <- grid.view.zipWithIndex
(cell, x) <- row.view.zipWithIndex
if cell != '#'
start = Pos(x, y)
xOffset <- -20 to 20
pos = start + Pos(xOffset, 0)
if grid.containsPos(pos)
startCheat = xOffset.abs
maxEndCheat = 20 - startCheat
yOffset <- (-maxEndCheat) to maxEndCheat
end = pos + Pos(0, yOffset)
if grid.containsPos(end) && grid(end) != '#'
endCheat = yOffset.abs
cheatDistance = forwardResult.distances(start) + (startCheat + endCheat) + backwardResult.distances(end)
//if cheatDistance <= noCheatDistance
save = noCheatDistance - cheatDistance
} yield Cheat(start, end, save)).toSet
}
}

def parseGrid(input: String): Grid[Char] = input.linesIterator.map(_.toVector).toVector

lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day20.txt")).mkString.trim

def main(args: Array[String]): Unit = {
println(countGoodCheats(parseGrid(input)))
println(Part1.countGoodCheats(parseGrid(input)))
println(Part2.countGoodCheats(parseGrid(input)))
}
}
26 changes: 24 additions & 2 deletions src/test/scala/eu/sim642/adventofcode2024/Day20Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Day20Test extends AnyFunSuite {
|###############""".stripMargin

test("Part 1 examples") {
val cheats = findCheats(parseGrid(exampleInput)).groupCount(_.save)
val cheats = Part1.findCheats(parseGrid(exampleInput)).groupCount(_.save)
assert(cheats(2) == 14)
assert(cheats(4) == 14)
assert(cheats(6) == 2)
Expand All @@ -39,6 +39,28 @@ class Day20Test extends AnyFunSuite {
}

test("Part 1 input answer") {
assert(countGoodCheats(parseGrid(input)) == 1490)
assert(Part1.countGoodCheats(parseGrid(input)) == 1490)
}

test("Part 2 examples") {
val cheats = Part2.findCheats(parseGrid(exampleInput)).groupCount(_.save)
assert(cheats(50) == 32)
assert(cheats(52) == 31)
assert(cheats(54) == 29)
assert(cheats(56) == 39)
assert(cheats(58) == 25)
assert(cheats(60) == 23)
assert(cheats(62) == 20)
assert(cheats(64) == 19)
assert(cheats(66) == 12)
assert(cheats(68) == 14)
assert(cheats(70) == 12)
assert(cheats(72) == 22)
assert(cheats(74) == 4)
assert(cheats(76) == 3)
}

ignore("Part 2 input answer") { // TODO: optimize (~4.3s)
assert(Part2.countGoodCheats(parseGrid(input)) == 1011325)
}
}

0 comments on commit eedd1fe

Please sign in to comment.