diff --git a/build.gradle.kts b/build.gradle.kts index cb249d4..2cf0978 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { kotlin("jvm") - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("org.jmailen.kotlinter") + id("com.adarshr.test-logger") application } @@ -35,10 +36,6 @@ tasks.test { maxHeapSize = "20g" } -tasks.withType { - kotlinOptions.jvmTarget = "21" -} - tasks.withType { standardInput = System.`in` } diff --git a/kls_database.db b/kls_database.db deleted file mode 100644 index 68fdb7b..0000000 Binary files a/kls_database.db and /dev/null differ diff --git a/src/main/kotlin/cz/glubo/adventofcode/Main.kt b/src/main/kotlin/cz/glubo/adventofcode/Main.kt index bc226ee..430b350 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/Main.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/Main.kt @@ -60,6 +60,8 @@ import cz.glubo.adventofcode.y2024.day15.y2024day15part1 import cz.glubo.adventofcode.y2024.day15.y2024day15part2 import cz.glubo.adventofcode.y2024.day16.y2024day16part1 import cz.glubo.adventofcode.y2024.day16.y2024day16part2 +import cz.glubo.adventofcode.y2024.day17.y2024day17part1 +import cz.glubo.adventofcode.y2024.day17.y2024day17part2 import cz.glubo.adventofcode.y2024.day2.y2024day2part1 import cz.glubo.adventofcode.y2024.day2.y2024day2part2 import cz.glubo.adventofcode.y2024.day3.y2024day3part1 @@ -137,6 +139,13 @@ class InputToLongCommand( override suspend fun execute(input: Input) = action(input) } +@Command(mixinStandardHelpOptions = true) +class InputToStringCommand( + private val action: suspend (input: Input) -> String, +) : InputCommand() { + override suspend fun execute(input: Input) = action(input) +} + @Command(mixinStandardHelpOptions = true) class LinesToIntCommand( private val action: suspend (Flow) -> Int, @@ -252,6 +261,8 @@ fun main(args: Array) { "2024day15p2" to InputToLongCommand { y2024day15part2(it) }, "2024day16p1" to InputToLongCommand { y2024day16part1(it) }, "2024day16p2" to InputToLongCommand { y2024day16part2(it) }, + "2024day17p1" to InputToStringCommand { y2024day17part1(it) }, + "2024day17p2" to InputToLongCommand { y2024day17part2(it).toLong() }, ) val cmd = CommandLine(MyHelpCommand()) diff --git a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt index fdabf84..7f78ad9 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt @@ -261,3 +261,7 @@ fun IntRange.split(boundary: Int): Pair = } fun Int?.orMax() = this ?: Int.MAX_VALUE + +fun List.isSame(other: List) = (this.size == other.size) && this.indices.all { + this[it] == other[it] +} diff --git a/src/main/kotlin/cz/glubo/adventofcode/y2023/day4/Day4.kt b/src/main/kotlin/cz/glubo/adventofcode/y2023/day4/Day4.kt index d483df0..f2ae627 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/y2023/day4/Day4.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/y2023/day4/Day4.kt @@ -77,5 +77,6 @@ fun pow2(n: Int): Int = } else if (n == 0) { 1 } else { - (0.. acc * 2 } +// (0.. acc * 2 } + 1.shl(n) } diff --git a/src/main/kotlin/cz/glubo/adventofcode/y2024/day17/Day17.kt b/src/main/kotlin/cz/glubo/adventofcode/y2024/day17/Day17.kt new file mode 100644 index 0000000..801daf6 --- /dev/null +++ b/src/main/kotlin/cz/glubo/adventofcode/y2024/day17/Day17.kt @@ -0,0 +1,235 @@ +package cz.glubo.adventofcode.y2024.day17 + +import cz.glubo.adventofcode.utils.input.Input +import cz.glubo.adventofcode.utils.isSame +import cz.glubo.adventofcode.y2024.day17.ParameterType.COMBO +import cz.glubo.adventofcode.y2024.day17.ParameterType.LITERAL +import io.klogging.noCoLogger +import kotlinx.coroutines.flow.toList +import java.math.BigInteger + +val logger = noCoLogger({}.javaClass.toString()) + +enum class ParameterType { + LITERAL, + COMBO, +} + +enum class OPCODE( + val opcode: Int, + val type: ParameterType, +) { + ADV(0, COMBO), + BXL(1, LITERAL), + BST(2, COMBO), + JNZ(3, LITERAL), + BXC(4, LITERAL), + OUT(5, COMBO), + BDV(6, COMBO), + CDV(7, COMBO), +} + +data class State( + var A: Long, + var B: Long, + var C: Long, + var pointer: Int, + var output: String, +) + +typealias Program = List> + +fun combo( + state: State, + param: Int, +): Long = + when (param) { + 0, 1, 2, 3 -> param.toLong() + 4 -> state.A + 5 -> state.B + 6 -> state.C + 7 -> error("reserved") + else -> error("unknow combo value $param") + } + +infix fun String.join(other: String) = + if (this.isNotBlank()) { + "$this,$other" + } else { + other + } + +fun advance( + state: State, + program: Program, +): Boolean { + val op = program.getOrNull((state.pointer / 2).toInt()) + logger.debug { "op $op state $state" } + return when (op?.first) { + OPCODE.ADV -> { + state.A /= pow2(combo(state, op.second)) + state.pointer += 2 + true + } + + OPCODE.BXL -> { + state.B = state.B.xor(op.second.toLong()) + state.pointer += 2 + true + } + + OPCODE.BST -> { + state.B = combo(state, op.second) % 8.toLong() + state.pointer += 2 + true + } + + OPCODE.JNZ -> { + if (state.A == 0L) { + state.pointer += 2 + true + } else { + state.pointer = op.second + true + } + } + + OPCODE.BXC -> { + state.B = state.B.xor(state.C) + state.pointer += 2 + true + } + + OPCODE.OUT -> { + state.output = state.output join combo(state, op.second).mod(8.toLong()).toString() + state.pointer += 2 + true + } + + OPCODE.BDV -> { + state.B = state.A / pow2(combo(state, op.second)) + state.pointer += 2 + true + } + + OPCODE.CDV -> { + state.C = state.A / pow2(combo(state, op.second)) + state.pointer += 2 + true + } + + null -> { + false + } + } +} + +fun String.parseProgramRaw() = + this + .removePrefix("Program: ") + .split(",") + .map { it.toInt() } + +fun List.toProgram() = this + .chunked(2) + .map { (o, p) -> + OPCODE.entries.first { it.opcode == o } to p + } + +fun String.parseProgram() = + this.parseProgramRaw() + .toProgram() + +suspend fun y2024day17part1(input: Input): String { + logger.info("year 2024 day 17 part 1") + val lines = input.lineFlow().toList() + val a = lines[0].removePrefix("Register A: ").toLong() + val b = lines[1].removePrefix("Register B: ").toLong() + val c = lines[2].removePrefix("Register C: ").toLong() + val program: Program = + lines[4].parseProgram() + + val currentState = + State( + A = a, + B = b, + C = c, + pointer = 0, + output = "", + ) + var i = 0 + while (i < 100) { + val runNext = advance(currentState, program) + if (!runNext) { + return currentState.output + } + i++ + } + return currentState.output +} + +fun run( + program: Program, + a: Long, +): List { + val currentState = + State( + A = a, + B = 0L, + C = 0L, + pointer = 0, + output = "", + ) + var i = 0 + while (i < 1000) { + val runNext = advance(currentState, program) + if (!runNext) { + return currentState.output.split(",").map { it.toInt() } + } + i++ + } + return currentState.output.split(",").map { it.toInt() } +} + +fun pow2(n: Long): Long = + if (n < 0) { + throw IllegalArgumentException() + } else if (n == 0L) { + 1L + } else { + 1L.shl(n.toInt()) + } + +suspend fun y2024day17part2(input: Input): Long { + val lines = input.lineFlow().toList() + val wanted = lines[4].parseProgramRaw() + val program: Program = wanted.toProgram() + + fun findMatch(wanted: List): List { + return when (wanted.size) { + 0 -> listOf(0L) + else -> { + val smallerACandidates = findMatch(wanted.drop(1)) + + smallerACandidates.flatMap { smallerA -> + (0..7).mapNotNull { i -> + val a = smallerA * 8.toLong() + i.toLong() + val output = run(program, a) + logger.debug { "$smallerA, $a: $output, $wanted}" } + if (wanted.isSame(output)) { + logger.debug { "HIT" } + a + } else { + null + } + } + } + } + } + + } + + val suitableAList = findMatch(wanted) + logger.debug { suitableAList } + return suitableAList.minOrNull() ?: error("not found") +} diff --git a/src/test/kotlin/cz/glubo/adventofcode/y2024/day17/Day17Test.kt b/src/test/kotlin/cz/glubo/adventofcode/y2024/day17/Day17Test.kt new file mode 100644 index 0000000..046b8fe --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/y2024/day17/Day17Test.kt @@ -0,0 +1,119 @@ +package cz.glubo.adventofcode.y2024.day17 + +import cz.glubo.adventofcode.utils.input.TestInput +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe + +/** + * https://adventofcode.com/2024/day/17 + */ +class Day17Test : + StringSpec({ + + "a" { + val program = + listOf( + OPCODE.BST to 6, + ) + val state = + State( + 0.toLong(), + 0.toLong(), + 9.toLong(), + 0, + "", + ) + val nextState = advance(state, program) + nextState shouldNotBe false + state.B shouldBe 1.toLong() + } + + "a2" { + val program = "1,7".parseProgram() + + val state = + State( + 0.toLong(), + 29.toLong(), + 0.toLong(), + 0, + "", + ) + val nextState = advance(state, program) + nextState shouldNotBe false + state.B shouldBe 26.toLong() + } + "a3" { + val program = "4,0".parseProgram() + + val state = + State( + 0.toLong(), + 2024.toLong(), + 43690.toLong(), + 0, + "", + ) + val nextState = advance(state, program) + nextState shouldNotBe false + state.B shouldBe 44354.toLong() + } + + "day17 example 2 part 1 matches" { + y2024day17part1( + TestInput( + """ + Register A: 10 + Register B: 0 + Register C: 0 + + Program: 5,0,5,1,5,4 + """.trimIndent(), + ), + ) shouldBe "0,1,2" + } + + "day17 example 3 part 1 matches" { + y2024day17part1( + TestInput( + """ + Register A: 2024 + Register B: 0 + Register C: 0 + + Program: 0,1,5,4,3,0 + """.trimIndent(), + ), + ) shouldBe "4,2,5,6,7,7,7,7,3,1,0" + } + + "day17 example part 1 matches" { + y2024day17part1( + TestInput( + """ + Register A: 729 + Register B: 0 + Register C: 0 + + Program: 0,1,5,4,3,0 + """.trimIndent(), + ), + ) shouldBe "4,6,3,5,6,3,5,2,1,0" + } + + "day17 example part 2 matches" { + y2024day17part2( + TestInput( + """ + Register A: 2024 + Register B: 0 + Register C: 0 + + Program: 0,3,5,4,3,0 + """.trimIndent(), + ), + ) shouldBe 117440.toLong() + } + + }) diff --git a/versions.properties b/versions.properties index 3560e39..be6431d 100644 --- a/versions.properties +++ b/versions.properties @@ -7,6 +7,9 @@ #### suppress inspection "SpellCheckingInspection" for whole file #### suppress inspection "UnusedProperty" for whole file +plugin.com.adarshr.test-logger=4.0.0 + + plugin.org.jmailen.kotlinter=4.5.0 ## unused @@ -35,8 +38,19 @@ version.kotest=5.9.1 version.kotlinx.coroutines=1.9.0 -version.kotlin=2.0.20 +version.kotlin=2.0.21 +## # available=2.0.21-RC +## # available=2.0.21 +## # available=2.1.0-Beta1 +## # available=2.1.0-Beta2 +## # available=2.1.0-RC +## # available=2.1.0-RC2 +## # available=2.1.0 version.info.picocli..picocli=4.7.6 -plugin.com.github.johnrengelman.shadow=8.1.1 +plugin.com.gradleup.shadow=8.3.5 +## # available=9.0.0-beta1 +## # available=9.0.0-beta2 +## # available=9.0.0-beta3 +## # available=9.0.0-beta4