diff --git a/README.md b/README.md index 55d18bf7..cefcbdfb 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,4 @@ Development occurs in language-specific directories: |[Day18.hs](hs/src/Day18.hs)|[Day18.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day18.kt)|[day18.py](py/aoc2024/day18.py)|[day18.rs](rs/src/day18.rs)| |[Day19.hs](hs/src/Day19.hs)|[Day19.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day19.kt)|[day19.py](py/aoc2024/day19.py)|[day19.rs](rs/src/day19.rs)| |[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day20.kt)|[day20.py](py/aoc2024/day20.py)|[day20.rs](rs/src/day20.rs)| -|[Day21.hs](hs/src/Day21.hs)|||| +|[Day21.hs](hs/src/Day21.hs)|[Day21.kt](kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Day21.kt)||| diff --git a/kt/aoc2024-exe/src/commonBench/kotlin/com/github/ephemient/aoc2024/exe/Day21Bench.kt b/kt/aoc2024-exe/src/commonBench/kotlin/com/github/ephemient/aoc2024/exe/Day21Bench.kt new file mode 100644 index 00000000..5f52c712 --- /dev/null +++ b/kt/aoc2024-exe/src/commonBench/kotlin/com/github/ephemient/aoc2024/exe/Day21Bench.kt @@ -0,0 +1,31 @@ +package com.github.ephemient.aoc2024.exe + +import com.github.ephemient.aoc2024.Day21 +import kotlinx.benchmark.Benchmark +import kotlinx.benchmark.Blackhole +import kotlinx.benchmark.Scope +import kotlinx.benchmark.Setup +import kotlinx.benchmark.State + +@State(Scope.Benchmark) +class Day21Bench { + private lateinit var input: String + + @Setup + fun setup() { + input = getDayInput(21) + } + + @Benchmark + fun part1() = Day21(input).part1() + + @Benchmark + fun part2() = Day21(input).part2() + + @Benchmark + fun solve(bh: Blackhole) { + val day21 = Day21(input) + bh.consume(day21.part1()) + bh.consume(day21.part2()) + } +} \ No newline at end of file diff --git a/kt/aoc2024-lib/build.gradle.kts b/kt/aoc2024-lib/build.gradle.kts index 9ea8efa9..b951c200 100644 --- a/kt/aoc2024-lib/build.gradle.kts +++ b/kt/aoc2024-lib/build.gradle.kts @@ -4,7 +4,16 @@ plugins { } kotlin { - jvm() + jvm { + val codegen by compilations.creating + val runCodegen by tasks.registering(JavaExec::class) { + mainClass = "com.github.ephemient.aoc2024.codegen.Main" + classpath(codegen.output.allOutputs, codegen.runtimeDependencyFiles) + outputs.dir(layout.buildDirectory.dir("build/sources/codegen")) + argumentProviders.add { listOf(outputs.files.singleFile.path) } + } + kotlin.sourceSets.getByName("commonMain").kotlin.srcDirs(runCodegen) + } wasmJs { browser() nodejs() @@ -54,6 +63,12 @@ kotlin { runtimeOnly(libs.junit.jupiter.engine) } } + + getByName("jvmCodegen") { + dependencies { + implementation(libs.kotlinpoet) + } + } } val detektTaskNames = targets.flatMap { target -> diff --git a/kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Days.kt b/kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Days.kt index 5e3af788..c50e4fa3 100644 --- a/kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Days.kt +++ b/kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Days.kt @@ -21,6 +21,7 @@ val days: List = listOf( Day(18, ::Day18, Day18::part1, Day18::part2), Day(19, ::Day19, Day19::solve), Day(20, ::Day20, Day20::part1, Day20::part2), + Day(21, ::Day21, Day21::part1, Day21::part2), ) data class Day( diff --git a/kt/aoc2024-lib/src/commonTest/kotlin/com/github/ephemient/aoc2024/Day21Test.kt b/kt/aoc2024-lib/src/commonTest/kotlin/com/github/ephemient/aoc2024/Day21Test.kt new file mode 100644 index 00000000..a8c5d9f9 --- /dev/null +++ b/kt/aoc2024-lib/src/commonTest/kotlin/com/github/ephemient/aoc2024/Day21Test.kt @@ -0,0 +1,22 @@ +package com.github.ephemient.aoc2024 + +import kotlin.test.Test +import kotlin.test.assertEquals + +class Day21Test { + @Test + fun part1() { + assertEquals(126384, Day21(example).part1()) + } + + companion object { + private val example = + """ + |029A + |980A + |179A + |456A + |379A + |""".trimMargin() + } +} diff --git a/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Day21.kt b/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Day21.kt new file mode 100644 index 00000000..1653fc32 --- /dev/null +++ b/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Day21.kt @@ -0,0 +1,141 @@ +package com.github.ephemient.aoc2024.codegen + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.LONG_ARRAY +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.MemberName.Companion.member +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STRING +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.joinToCode +import java.nio.file.Path +import kotlin.math.abs + +fun day21(outputDir: Path, className: ClassName = ClassName("com.github.ephemient.aoc2024", "Day21")) { + FileSpec.builder(className) + .addType( + TypeSpec.classBuilder(className) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("input", STRING) + .build() + ) + .addProperty( + PropertySpec.builder("lines", LIST.parameterizedBy(STRING)) + .addModifiers(KModifier.PRIVATE) + .initializer("%N.%M()", "input", MemberName("kotlin.text", "lines")) + .build() + ) + .addFunctions( + arrayOf(IndexedValue(2, "part1"), IndexedValue(25, "part2")).map { (depth, name) -> + FunSpec.builder(name) + .returns(LONG) + .beginControlFlow("return %N.%M", "lines", MemberName("kotlin.collections", "sumOf")) + .addStatement("var %N = 10", "i") + .addStatement("var %N = 0", "numeric") + .addStatement("var %N = 0L", "length") + .beginControlFlow("for (%N in %N)", "char", "it") + .beginControlFlow("val %N = when (%N)", "j", "char") + .beginControlFlow("in '0'..'9' ->") + .addStatement("%1N = 10 * %1N + (%2N - '0')", "numeric", "char") + .addStatement("%N - '0'", "char") + .endControlFlow() + .addStatement("else -> 10") + .endControlFlow() + .addStatement( + "%N += %M[11 * %N + %N]", + "length", + className.nestedClass("Companion").member(name), + "i", + "j", + ) + .addStatement("%N = %N", "i", "j") + .endControlFlow() + .addStatement("%N * %N", "numeric", "length") + .endControlFlow() + .build() + } + ) + .addType( + TypeSpec.companionObjectBuilder() + .addProperty( + PropertySpec.builder("part1", LONG_ARRAY) + .addModifiers(KModifier.PRIVATE) + .initializer( + "%M(%L)", + MemberName("kotlin", "longArrayOf"), + lut(2).joinToCode(",♢") { CodeBlock.of("%L", it) }, + ) + .build() + ) + .addProperty( + PropertySpec.builder("part2", LONG_ARRAY) + .addModifiers(KModifier.PRIVATE) + .initializer( + "%M(%L)", + MemberName("kotlin", "longArrayOf"), + lut(25).joinToCode(",♢") { CodeBlock.of("%L", it) }, + ) + .build() + ) + .build() + ) + .build() + ) + .build() + .writeTo(outputDir) +} + +private val keypad = intArrayOf( + 3 * 1 + 1, // 0 + 3 * 2 + 0, // 1 + 3 * 2 + 1, // 2 + 3 * 2 + 2, // 3 + 3 * 3 + 0, // 4 + 3 * 3 + 1, // 5 + 3 * 3 + 2, // 6 + 3 * 4 + 0, // 7 + 3 * 4 + 1, // 8 + 3 * 4 + 2, // 9 + 3 * 1 + 2, // A +) +private const val BLANK = 3 * 1 + 0 +private const val UP = 3 * 1 + 1 +private const val A = 3 * 1 + 2 +private const val LEFT = 3 * 0 + 0 +private const val DOWN = 3 * 0 + 1 +private const val RIGHT = 3 * 0 + 2 + +private fun lut(depth: Int): List { + var lut = Array(15 * 15) { + val p1 = it / 15 + val p2 = it % 15 + if (p1 == BLANK || p2 == BLANK) null else abs(p2 / 3 - p1 / 3) + abs(p2 % 3 - p1 % 3) + 1L + } + fun best(p1: Int, p2: Int, pos: Int): Long? { + if (p1 == BLANK || p2 == BLANK) return null + if (p1 == p2) return lut[15 * pos + A] + val v = when { + p1 / 3 < p2 / 3 -> lut[15 * pos + UP]?.let { best(p1 + 3, p2, UP)?.let(it::plus) } + p1 / 3 > p2 / 3 -> lut[15 * pos + DOWN]?.let { best(p1 - 3, p2, DOWN)?.let(it::plus) } + else -> null + } + val h = when { + p1 % 3 < p2 % 3 -> lut[15 * pos + RIGHT]?.let { best(p1 + 1, p2, RIGHT)?.let(it::plus) } + p1 % 3 > p2 % 3 -> lut[15 * pos + LEFT]?.let { best(p1 - 1, p2, LEFT)?.let(it::plus) } + else -> null + } + return if (v != null && h != null) minOf(v, h) else v ?: h + } + repeat(depth) { lut = Array(15 * 15) { best(it / 15, it % 15, A) } } + return List(11 * 11) { lut[15 * keypad[it / 11] + keypad[it % 11]]!! }.also { + println(it.chunked(11).joinToString(",\n", "Day21[$depth] = [\n", "]") { it.joinToString() }) + } +} diff --git a/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Main.kt b/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Main.kt new file mode 100644 index 00000000..df67c5a0 --- /dev/null +++ b/kt/aoc2024-lib/src/jvmCodegen/kotlin/com/github/ephemient/aoc2024/codegen/Main.kt @@ -0,0 +1,16 @@ +@file:JvmName("Main") + +package com.github.ephemient.aoc2024.codegen + +import java.nio.file.Files +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path +import kotlin.io.path.deleteRecursively + +@OptIn(ExperimentalPathApi::class) +fun main(argv: Array) { + val outputDir = Path(argv.single()) + outputDir.deleteRecursively() + Files.createDirectory(outputDir) + day21(outputDir) +} diff --git a/kt/gradle/libs.versions.toml b/kt/gradle/libs.versions.toml index efd8d1c6..7f15a64b 100644 --- a/kt/gradle/libs.versions.toml +++ b/kt/gradle/libs.versions.toml @@ -4,6 +4,7 @@ junit-jupiter = "5.11.4" kotlin = "2.1.0" kotlinx-benchmark = "0.4.13" kotlinx-coroutines = "1.10.1" +kotlinpoet = "2.0.0" native-image = "0.10.4" okio = "3.9.1" @@ -19,6 +20,7 @@ detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } kotlin-wrappers-node = { module = "org.jetbrains.kotlin-wrappers:kotlin-node", version = "22.5.5-pre.854" } +kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } kotlinx-benchmark = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinx-benchmark" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }