From fc7b1e42dd3c555ec1ce31f6aa606368dd6be42b Mon Sep 17 00:00:00 2001 From: Stephan Linkel <251381+norganos@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:15:20 +0100 Subject: [PATCH] day 21 was a tough one --- src/main/kotlin/de/linkel/aoc/Day21.kt | 184 ++++++++++++++++++++- src/test/kotlin/de/linkel/aoc/Day21Test.kt | 23 ++- 2 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/de/linkel/aoc/Day21.kt b/src/main/kotlin/de/linkel/aoc/Day21.kt index 34ac74b..b118343 100644 --- a/src/main/kotlin/de/linkel/aoc/Day21.kt +++ b/src/main/kotlin/de/linkel/aoc/Day21.kt @@ -2,13 +2,191 @@ package de.linkel.aoc import de.linkel.aoc.base.AbstractLinesAdventDay import de.linkel.aoc.base.QuizPart +import de.linkel.aoc.utils.grid.Area +import de.linkel.aoc.utils.grid.Point import jakarta.inject.Singleton +import java.util.* @Singleton -class Day21: AbstractLinesAdventDay() { +class Day21: AbstractLinesAdventDay() { override val day = 21 - override fun process(part: QuizPart, lines: Sequence): Int { - return 0 + override fun process(part: QuizPart, lines: Sequence): Long { + var width = 0 + var height = 0 + var start = Point(0,0) + val rocks = lines + .flatMapIndexed { y, line -> + if (width == 0) width = line.length + if (height <= y) height = y + 1 + line + .mapIndexed { x, c -> + if (c == 'S') + start = Point(x, y) + if (c == '#') + Point(x, y) + else null + } + .filterNotNull() + } + .toSet() + + val area = Area(0, 0, width, height) + return if (part == QuizPart.A) + dijkstra1(area, rocks, start, if (width < 15) 6 else 64).size.toLong() + else { + assert(width == height) + assert(width % 2 == 1) + assert(height % 2 == 1) + val max = if (width < 15) 5000 else 26501365 + val step = width * 2 + val rem = max % step + + if (width < 15) { + println(" 6: ${dijkstra2(area, rocks, start, 6)}") + println(" 10: ${dijkstra2(area, rocks, start, 10)}") + println(" 50: ${dijkstra2(area, rocks, start, 50)}") + println("100: ${dijkstra2(area, rocks, start, 100)}") + println("500: ${dijkstra2(area, rocks, start, 500)}") + } + + val probe = 5 + (0..probe) + .map { i -> dijkstra2(area, rocks, start, step * i + rem) } + .toSeq() + .prepare() + .toExtrapolation() + .extrapolate((max - rem) / step - probe) + } + } + + fun dijkstra1(area: Area, rocks: Set, start: Point, max: Int): Set { + val queue = PriorityQueue>(compareBy { it.second }) + queue.add(start to 0) + val seen = mutableSetOf(start) + val visited = mutableSetOf() + while (queue.isNotEmpty()) { + val (point, distance) = queue.remove() + if (distance > max) { + break + } + if (distance % 2 == max % 2) { + visited.add(point) + } + + listOf( + point + NORTH, + point + WEST, + point + SOUTH, + point + EAST + ) + .filter { it in area } + .filter { it !in rocks } + .filter { it !in seen } + .forEach { + queue.add(it to distance + 1) + seen.add(it) + } + } + return visited + } + + fun dijkstra2(area: Area, rocks: Set, start: Point, max: Int): Long { + val queue = PriorityQueue>(compareBy { it.second }) + queue.add(start to 0) + val seen = mutableSetOf(start) + var count = 0L + while (queue.isNotEmpty()) { + val (point, distance) = queue.remove() + if (distance > max) { + break + } + if (distance % 2 == max % 2) { + count++ + } + + listOf( + point + NORTH, + point + WEST, + point + SOUTH, + point + EAST + ) + .filter { + if (it in area) + it !in rocks + else { + val p = Point(it.x pmod area.width, it.y pmod area.height) + p in area && p !in rocks + } + } + .filter { it !in seen } + .forEach { + queue.add(it to distance + 1) + seen.add(it) + } + } + return count + } + + private infix fun Int.pmod(other: Int): Int { + val result = this % other + return if (result < 0) + result + other + else result + } + + private fun List.toSeq(): Seq = Seq(this) + + class Seq( + input: List, + val parent: Seq? = null + ) { + var values = input.toMutableList() + private set + + fun prepare(): Seq { + return if (values.last() == 0L) this + else differentiate().prepare() + } + + fun differentiate(): Seq { + val diffs = values + .windowed(2) + .map { (a, b) -> b - a } + .toList() + if (diffs.isEmpty()) + throw Exception("not enough input iterations") + return Seq( + input = diffs, + parent = this + ) + } + + private fun lasts(): List + = listOf(values.last()) + (parent?.lasts() ?: emptyList()) + + fun toExtrapolation(): Extrapolation { + return Extrapolation(lasts()) + } + } + + class Extrapolation( + val inputs: List + ) { + init { + assert(inputs.first() == 0L) + } + + fun extrapolate(steps: Int): Long { + val vals = inputs.toMutableList() + repeat(steps) { + vals.indices.forEach { i -> + if (i < vals.lastIndex) { + vals[i+1] += vals[i] + } + } + } + return vals.last() + } } } diff --git a/src/test/kotlin/de/linkel/aoc/Day21Test.kt b/src/test/kotlin/de/linkel/aoc/Day21Test.kt index 37cd807..be13d98 100644 --- a/src/test/kotlin/de/linkel/aoc/Day21Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day21Test.kt @@ -1,13 +1,26 @@ package de.linkel.aoc -class Day21Test: AbstractDayTest() { +class Day21Test: AbstractDayTest() { override val exampleA = """ +........... +.....###.#. +.###.##..#. +..#.#...#.. +....#.#.... +.##..S####. +.##..#...#. +.......##.. +.##.#.####. +.##..##.##. +........... """.trimIndent() - override val exampleSolutionA = 0 - override val solutionA = 0 + override val exampleSolutionA = 16L + override val solutionA = 3773L - override val exampleSolutionB = 0 - override val solutionB = 0 + override val exampleSolutionB = 16733044L + override val solutionB = 625628021226274L override val implementation = Day21() } +// 6872 +// 3774