From 76c01e01e5fa46d7f0d779b9e1a2ec6483a4ec6e Mon Sep 17 00:00:00 2001 From: chrisly42 Date: Mon, 16 Dec 2024 07:18:41 +0100 Subject: [PATCH] Day 16. --- src/aoc2024/Day13.kt | 13 ++++ src/aoc2024/Day16.kt | 162 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 src/aoc2024/Day16.kt diff --git a/src/aoc2024/Day13.kt b/src/aoc2024/Day13.kt index b3cde5a..453699b 100644 --- a/src/aoc2024/Day13.kt +++ b/src/aoc2024/Day13.kt @@ -80,6 +80,19 @@ Prize: X=18641, Y=10279 // val t2 = bx - by // val t3 = px - py + // Linear Diophantine equations + /* The simplest linear Diophantine equation takes the form + a*x + b*x = c + where a, b and c are given integers. + The solutions are described by the following theorem: + This Diophantine equation has a solution (where x and y are integers), + IFF c is a multiple of the greatest common divisor of a and b. + Moreover, if (x, y) is a solution, then the other solutions have the + form (x + kv, y − ku), where + - k is an arbitrary integer, and + - u and v are the quotients of a and b (respectively) by the greatest common divisor of a and b. + */ + // With our input data, all equations have exactly one solution val pad = (px * by - py * bx) val pan = (ax * by - ay * bx) if (pad % pan == 0L) { diff --git a/src/aoc2024/Day16.kt b/src/aoc2024/Day16.kt new file mode 100644 index 0000000..f3e3c53 --- /dev/null +++ b/src/aoc2024/Day16.kt @@ -0,0 +1,162 @@ +package aoc2024 + +import CharGrid +import RelPos +import println +import readInput + +/* +--- Day 16: Reindeer Maze --- +https://adventofcode.com/2024/day/16 +*/ +fun main() { + + val inlineTestInput = """ +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +############### +""" + val inlineTestInput2 = """ +################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +################# +""" + + data class Node(val pos: RelPos, val dir: RelPos, val relLen: Int, val path: LinkedHashSet>) + + val costMap = hashMapOf( + RelPos(0, -1) to listOf(RelPos(0, -1) to 1, RelPos(1, 0) to 1001, RelPos(-1, 0) to 1001, RelPos(0, 1) to 2001), + RelPos(1, 0) to listOf(RelPos(1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(-1, 0) to 2001), + RelPos(0, 1) to listOf(RelPos(0, 1) to 1, RelPos(-1, 0) to 1001, RelPos(1, 0) to 1001, RelPos(0, -1) to 2001), + RelPos(-1, 0) to listOf(RelPos(-1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(1, 0) to 2001) + ) + + fun part1(input: List): Int { + val grid = CharGrid(input, '#') + val queue = ArrayDeque() + val (startc, startr) = grid.findMatches { it == 'S' }[0] + val (endc, endr) = grid.findMatches { it == 'E' }[0] + val endpos = RelPos(endc, endr) + + grid[startc, startr] = '.' + grid[endpos] = '.' + queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet())) + val bestCosts = HashMap, Int>() + var minCost = Int.MAX_VALUE + while (queue.isNotEmpty()) { + val node = queue.removeFirst() + val pos = node.pos + val dir = node.dir + val cost = node.relLen + val path = node.path + path.add(pos to dir) + if (pos == endpos) { + minCost = minCost.coerceAtMost(cost) + continue + } + if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) <= cost) { + continue + } + bestCosts[pos to dir] = cost + val nextDirs = + costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) } + .map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) } + + queue.addAll(nextDirs) + } + + return minCost + } + + fun part2(input: List): Int { + val grid = CharGrid(input, '#') + val queue = ArrayDeque() + val (startc, startr) = grid.findMatches { it == 'S' }[0] + val (endc, endr) = grid.findMatches { it == 'E' }[0] + val endpos = RelPos(endc, endr) + + grid[startc, startr] = '.' + grid[endpos] = '.' + queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet())) + val bestCosts = HashMap, Int>() + var minCost = Int.MAX_VALUE + val bestNodes = HashSet() + while (queue.isNotEmpty()) { + val node = queue.removeFirst() + val pos = node.pos + val dir = node.dir + val cost = node.relLen + val path = node.path + path.add(pos to dir) + if (pos == endpos) { + if (cost < minCost) { + bestNodes.clear() + bestNodes.addAll(path.map { it.first }) + } + if (minCost == cost) { + bestNodes.addAll(path.map { it.first }) + } + minCost = minCost.coerceAtMost(cost) + continue + } + if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) < cost) { + continue + } + bestCosts[pos to dir] = cost + val nextDirs = + costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) } + .map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) } + + queue.addAll(nextDirs) + } + + return bestNodes.size + } + + // test if implementation meets criteria from the description, like: + val testInput = inlineTestInput.trim().reader().readLines() + val testInput2 = inlineTestInput2.trim().reader().readLines() + //val testInput = readInput("aoc2024/Day16_test") + val testInputPart1Result = part1(testInput) + println("Part 1 Test: $testInputPart1Result") + val testInputPart1Result2 = part1(testInput2) + println("Part 1 Test 2: $testInputPart1Result2") + val testInputPart2Result = part2(testInput) + println("Part 2 Test: $testInputPart2Result") + val testInputPart2Result2 = part2(testInput2) + println("Part 2 Test2: $testInputPart2Result2") + check(testInputPart1Result == 7036) + check(testInputPart1Result2 == 11048) + check(testInputPart2Result == 45) + check(testInputPart2Result2 == 64) + + val input = readInput("aoc2024/Day16") + part1(input).println() + part2(input).println() +}