From 3df55d71cc70d33ae899e8f18c91e056a226d6b7 Mon Sep 17 00:00:00 2001 From: Petr Sykora Date: Tue, 24 Dec 2024 09:34:57 +0100 Subject: [PATCH 1/3] wip --- src/main/kotlin/cz/glubo/adventofcode/Main.kt | 4 + .../cz/glubo/adventofcode/utils/Utils.kt | 20 ++ .../glubo/adventofcode/y2024/day24/Day24.kt | 257 ++++++++++++++++++ .../glubo/adventofcode/utils/AllOrdersTest.kt | 48 ++++ .../cz/glubo/adventofcode/utils/GetBitTest.kt | 22 ++ .../adventofcode/y2024/day24/Day24Test.kt | 77 ++++++ 6 files changed, 428 insertions(+) create mode 100644 src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt create mode 100644 src/test/kotlin/cz/glubo/adventofcode/utils/AllOrdersTest.kt create mode 100644 src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt create mode 100644 src/test/kotlin/cz/glubo/adventofcode/y2024/day24/Day24Test.kt diff --git a/src/main/kotlin/cz/glubo/adventofcode/Main.kt b/src/main/kotlin/cz/glubo/adventofcode/Main.kt index 8b5fee3..74001c2 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/Main.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/Main.kt @@ -76,6 +76,8 @@ import cz.glubo.adventofcode.y2024.day22.y2024day22part1 import cz.glubo.adventofcode.y2024.day22.y2024day22part2 import cz.glubo.adventofcode.y2024.day23.y2024day23part1 import cz.glubo.adventofcode.y2024.day23.y2024day23part2 +import cz.glubo.adventofcode.y2024.day24.y2024day24part1 +import cz.glubo.adventofcode.y2024.day24.y2024day24part2 import cz.glubo.adventofcode.y2024.day3.y2024day3part1 import cz.glubo.adventofcode.y2024.day3.y2024day3part2 import cz.glubo.adventofcode.y2024.day4.y2024day4part1 @@ -287,6 +289,8 @@ fun main(args: Array) { "2024day22p2" to InputToLongCommand { y2024day22part2(it) }, "2024day23p1" to InputToLongCommand { y2024day23part1(it) }, "2024day23p2" to InputToStringCommand { y2024day23part2(it) }, + "2024day24p1" to InputToLongCommand { y2024day24part1(it) }, + "2024day24p2" to InputToStringCommand { y2024day24part2(it, 4) }, ) 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 101634c..1c8920e 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt @@ -293,3 +293,23 @@ fun Set.allSubsets(): Sequence> = yield(setOf(first) + it) } } + +fun Set.allOrders(): Sequence> = + sequence { + val list = this@allOrders.toList() + if (list.isEmpty()) { + yield(emptyList()) + } else { + for (i in list.indices) { + val otherSet = + list + .filterIndexed { it, _ -> it != i } + .toSet() + otherSet.allOrders().forEach { item -> + yield(item + list [i]) + } + } + } + } + +fun Long.getBit(n: Int) = this.shr(n).and(1) diff --git a/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt b/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt new file mode 100644 index 0000000..11b928b --- /dev/null +++ b/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt @@ -0,0 +1,257 @@ +package cz.glubo.adventofcode.y2024.day24 + +import cz.glubo.adventofcode.utils.getBit +import cz.glubo.adventofcode.utils.input.Input +import io.klogging.noCoLogger +import kotlinx.coroutines.flow.toList +import kotlin.experimental.and +import kotlin.experimental.or +import kotlin.experimental.xor + +val logger = noCoLogger({}.javaClass.toString()) + +suspend fun y2024day24part1(input: Input): Long { + logger.info("year 2024 day 24 part 1") + + val lines = input.lineFlow().toList() + val stateLines = lines.takeWhile { it.isNotBlank() } + var gates = parseGates(lines.takeLastWhile { it.isNotBlank() }) + + val stateMap = parseStateMap(stateLines) + + val result = + compute( + stateMap, + gates, + emptyMap(), + ) + return result +} + +enum class Operation( + val compute: (Short, Short) -> Short, +) { + AND({ a, b -> a.and(b) }), + OR({ a, b -> a.or(b) }), + XOR({ a, b -> a.xor(b) }), +} + +data class Gate( + val inputA: String, + val inputB: String, + val operation: Operation, + val target: String, +) + +fun computeState( + inputStateMap: Map, + gates: List, + swapMap: Map, +): Map { + val stateMap = inputStateMap.toMutableMap() + var operationsLeft = gates + do { + var removed = false + + operationsLeft = + operationsLeft.filterIndexed { index, op -> + if (!stateMap.contains(op.inputA) || !stateMap.contains(op.inputB)) { + return@filterIndexed true + } + removed = true + val result = + op.operation.compute( + stateMap[op.inputA]!!, + stateMap[op.inputB]!!, + ) + + val target = swapMap.getOrDefault(op.target, op.target) + stateMap[target] = result + + false + } + } while (removed) + return stateMap +} + +fun compute( + inputStateMap: Map, + gates: List, + swapMap: Map, +): Long { + val stateMap = computeState(inputStateMap, gates, swapMap) + + return stateMap.variable("z") +} + +fun Map.variable(prefix: String) = + this + .filter { (key, value) -> + key.startsWith(prefix) + }.entries + .sortedByDescending { it.key } + .fold(0L) { acc, it -> acc.shl(1) + it.value } + +suspend fun y2024day24part2( + input: Input, + swaps: Int, +): String { + logger.info("year 2024 day 24 part 2") + val lines = input.lineFlow().toList() + val stateLines = lines.takeWhile { it.isNotBlank() } + var gates = parseGates(lines.takeLastWhile { it.isNotBlank() }) + + val stateMap = parseStateMap(stateLines) + + val x = stateMap.variable("x") + val y = stateMap.variable("y") + val wantedResult = x + y + + val badResult = compute(stateMap, gates, emptyMap()) + val badBitmap = badResult.xor(wantedResult) + + val wantedS = wantedResult.toString(2) + logger.info { "wantedResult: $wantedS" } + logger.info { "badbadResult: ${badResult.toString(2)}" } + logger.info { "badResultBmap ${badBitmap.toString(2).padStart(wantedS.length, '0')}" } + + val suspects = mutableSetOf>() + var rem = badBitmap + var i = 0 + do { + val cur = rem.and(1) + if (cur > 0) { + val name = "z" + i.toString().padStart(2, '0') + suspects.add(name to wantedResult.getBit(i).toShort()) + } + i++ + rem = rem.shr(1) + } while (rem > 0) + + val suspectMap = + suspects + .associateWith { suspect -> + getNameWhitelist(suspect.first, gates) + }.entries + .sortedBy { it.value.size } + logger.info(suspectMap.map { it.key to it.value.size }) + + suspectMap.first().let { sentry -> + val reducedState = + stateMap.entries + .filter { it.key in sentry.value } + .associate { it.key to it.value } + logger.info { "reduced state $reducedState" } + + val reducedGates = + gates.filter { + it.target in sentry.value + } + logger.info { "reduced gates $reducedGates" } + + sentry.value.forEach { a -> + sentry.value.forEach { b -> + val swappedResult = computeState(reducedState, reducedGates, mapOf(a to b, b to a)) + logger.info { "swapping $a $b result $swappedResult" } + if (swappedResult[sentry.key.first] == sentry.key.second) { + logger.info("swap $a $b") + } + } + } + } + +// logger.info { "Initial suspects: $suspects" } +// do { +// var newSuspects = mutableSetOf() +// +// suspects.forEach { suspect -> +// val suspectGate = +// gateLines.firstOrNull { it.target == suspect } +// ?: return@forEach +// if (suspectGate.inputA !in suspects) newSuspects.add(suspectGate.inputA) +// if (suspectGate.inputB !in suspects) newSuspects.add(suspectGate.inputB) +// } +// +// suspects.addAll(newSuspects) +// } while (newSuspects.isEmpty()) +// +// logger.info { "Found suspects: $suspects" } +// +// val swapPool = +// suspects +// .allSubsets() +// .filter { it.size == swaps * 2 } +// .firstNotNullOf { swapMembers -> +// swapMembers +// .allOrders() +// .firstOrNull { +// val swapMap = mutableMapOf() +// +// it.chunked(2).forEach { (a, b) -> +// swapMap[a] = b +// swapMap[b] = a +// } +// +// wantedResult == +// compute( +// stateMap, +// gateLines, +// swapMap, +// ) +// } +// } +// +// return swapPool +// .sorted() +// .joinToString(",") + return "" +} + +private fun parseStateMap(stateLines: List): MutableMap { + val stateMap = mutableMapOf() + + stateLines.forEach { line -> + val (name, value) = line.split(": ") + stateMap[name] = value.toShort() + } + return stateMap +} + +private fun parseGates(lines: List) = + lines + .map { line -> + val (a, operand, b, _, target) = line.split(" ") + Gate( + a, + b, + when (operand) { + "AND" -> Operation.AND + "OR" -> Operation.OR + "XOR" -> Operation.XOR + else -> error("unknown operand $operand") + }, + target, + ) + } + +private fun getNameWhitelist( + target: String, + gates: List, +): Set { + var suspects = mutableSetOf(target) + + do { + var newSuspects = mutableSetOf() + + suspects.forEach { suspect -> + val suspectGate = + gates.firstOrNull { it.target == suspect } + ?: return@forEach + if (suspectGate.inputA !in suspects) newSuspects.add(suspectGate.inputA) + if (suspectGate.inputB !in suspects) newSuspects.add(suspectGate.inputB) + } + + suspects.addAll(newSuspects) + } while (newSuspects.isNotEmpty()) + return suspects +} diff --git a/src/test/kotlin/cz/glubo/adventofcode/utils/AllOrdersTest.kt b/src/test/kotlin/cz/glubo/adventofcode/utils/AllOrdersTest.kt new file mode 100644 index 0000000..56587ee --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/utils/AllOrdersTest.kt @@ -0,0 +1,48 @@ +package cz.glubo.adventofcode.utils + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldHaveSize + +class AllOrdersTest : + FreeSpec({ + "empty" { + val result = + emptySet() + .allOrders() + .toList() + result shouldHaveSize 1 + result[0] shouldHaveSize 0 + } + + "one" { + val result = + setOf(1) + .allOrders() + .toList() + result shouldHaveSize 1 + result shouldContain listOf(1) + } + "two" { + val result = + setOf(1, 2) + .allOrders() + .toList() + result shouldHaveSize 2 + result shouldContain listOf(1, 2) + result shouldContain listOf(2, 1) + } + "three" { + val result = + setOf(1, 2, 3) + .allOrders() + .toList() + result shouldHaveSize 6 + result shouldContain listOf(1, 2, 3) + result shouldContain listOf(1, 3, 2) + result shouldContain listOf(2, 1, 3) + result shouldContain listOf(2, 3, 1) + result shouldContain listOf(3, 1, 2) + result shouldContain listOf(3, 2, 1) + } + }) diff --git a/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt b/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt new file mode 100644 index 0000000..63e2fcc --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt @@ -0,0 +1,22 @@ +package cz.glubo.adventofcode.utils + +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class GetBitTest : + FreeSpec({ + "examples" { + assertSoftly { + 1L.getBit(0) shouldBe 1 + 3L.getBit(0) shouldBe 1 + 1025L.getBit(0) shouldBe 1 + + 2L.getBit(1) shouldBe 1 + 2L.getBit(0) shouldBe 0 + + 1024L.getBit(10) shouldBe 1 + 1024L.getBit(0) shouldBe 0 + } + } + }) diff --git a/src/test/kotlin/cz/glubo/adventofcode/y2024/day24/Day24Test.kt b/src/test/kotlin/cz/glubo/adventofcode/y2024/day24/Day24Test.kt new file mode 100644 index 0000000..2fb1715 --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/y2024/day24/Day24Test.kt @@ -0,0 +1,77 @@ +package cz.glubo.adventofcode.y2024.day24 + +import cz.glubo.adventofcode.utils.input.TestInput +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +/** + * https://adventofcode.com/2024/day/24 + */ +class Day24Test : + StringSpec({ + + "day24 example part 1 matches" { + y2024day24part1( + TestInput( + """ + x00: 1 + x01: 0 + x02: 1 + x03: 1 + x04: 0 + y00: 1 + y01: 1 + y02: 1 + y03: 1 + y04: 1 + + ntg XOR fgs -> mjb + y02 OR x01 -> tnw + kwq OR kpj -> z05 + x00 OR x03 -> fst + tgd XOR rvg -> z01 + vdt OR tnw -> bfw + bfw AND frj -> z10 + ffh OR nrd -> bqk + y00 AND y03 -> djm + y03 OR y00 -> psh + bqk OR frj -> z08 + tnw OR fst -> frj + gnj AND tgd -> z11 + bfw XOR mjb -> z00 + x03 OR x00 -> vdt + gnj AND wpb -> z02 + x04 AND y00 -> kjc + djm OR pbm -> qhw + nrd AND vdt -> hwm + kjc AND fst -> rvg + y04 OR y02 -> fgs + y01 AND x02 -> pbm + ntg OR kjc -> kwq + psh XOR fgs -> tgd + qhw XOR tgd -> z09 + pbm OR djm -> kpj + x03 XOR y03 -> ffh + x00 XOR y04 -> ntg + bfw OR bqk -> z06 + nrd XOR fgs -> wpb + frj XOR qhw -> z04 + bqk OR frj -> z07 + y03 OR x01 -> nrd + hwm AND bqk -> z03 + tgd XOR rvg -> z12 + tnw OR pbm -> gnj + """.trimIndent(), + ), + ) shouldBe 2024 + } + +// "day24 example part 2 matches" { +// y2024day24part2( +// TestInput( +// """ +// """.trimIndent(), +// ), +// ) shouldBe 0 +// } + }) From 2341df62b55497b3dc49a961e716460fc312f8a8 Mon Sep 17 00:00:00 2001 From: Petr Sykora Date: Wed, 25 Dec 2024 06:06:11 +0100 Subject: [PATCH 2/3] feat: day 24 after submit --- build.gradle.kts | 3 +- .../cz/glubo/adventofcode/utils/Utils.kt | 12 ++ .../glubo/adventofcode/y2024/day24/Day24.kt | 190 ++++++++---------- .../cz/glubo/adventofcode/utils/GetBitTest.kt | 10 + 4 files changed, 106 insertions(+), 109 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2cf0978..ea1e3ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile - plugins { kotlin("jvm") id("com.gradleup.shadow") @@ -21,6 +19,7 @@ repositories { dependencies { implementation(kotlin("stdlib")) implementation(KotlinX.coroutines.core) + implementation(KotlinX.coroutines.reactive) implementation("info.picocli:picocli:_") implementation("io.klogging:slf4j-klogging:_") testImplementation(kotlin("test")) diff --git a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt index 1c8920e..e821c92 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt @@ -313,3 +313,15 @@ fun Set.allOrders(): Sequence> = } fun Long.getBit(n: Int) = this.shr(n).and(1) + +fun Long.bitDistance(other: Long): Int { + var a = this + var b = other + var result = 0 + do { + result += a.and(1).xor(b.and(1)).toInt() + a = a.shr(1) + b = b.shr(1) + } while (a > 0 || b > 0) + return result +} diff --git a/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt b/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt index 11b928b..736a3a0 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt @@ -1,9 +1,13 @@ package cz.glubo.adventofcode.y2024.day24 -import cz.glubo.adventofcode.utils.getBit +import cz.glubo.adventofcode.utils.bitDistance import cz.glubo.adventofcode.utils.input.Input import io.klogging.noCoLogger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking import kotlin.experimental.and import kotlin.experimental.or import kotlin.experimental.xor @@ -114,97 +118,91 @@ suspend fun y2024day24part2( logger.info { "wantedResult: $wantedS" } logger.info { "badbadResult: ${badResult.toString(2)}" } logger.info { "badResultBmap ${badBitmap.toString(2).padStart(wantedS.length, '0')}" } + val a = + findSwaps( + wantedResult, + stateMap, + gates, + emptyMap(), + 4, + ) - val suspects = mutableSetOf>() - var rem = badBitmap - var i = 0 - do { - val cur = rem.and(1) - if (cur > 0) { - val name = "z" + i.toString().padStart(2, '0') - suspects.add(name to wantedResult.getBit(i).toShort()) - } - i++ - rem = rem.shr(1) - } while (rem > 0) - - val suspectMap = - suspects - .associateWith { suspect -> - getNameWhitelist(suspect.first, gates) - }.entries - .sortedBy { it.value.size } - logger.info(suspectMap.map { it.key to it.value.size }) - - suspectMap.first().let { sentry -> - val reducedState = - stateMap.entries - .filter { it.key in sentry.value } - .associate { it.key to it.value } - logger.info { "reduced state $reducedState" } + return a!!.joinToString(",") +} - val reducedGates = - gates.filter { - it.target in sentry.value - } - logger.info { "reduced gates $reducedGates" } +suspend fun findSwaps( + wantedResult: Long, + stateMap: MutableMap, + gates: List, + swapMap: Map, + i: Int, +): List? { + val names = + gates + .map { it.target } + .filter { !swapMap.containsKey(it) } + + val swapErrorList = + (0.. + (i + 1.. + val newSwapMap = + mapOf( + names[i] to names[j], + names[j] to names[i], + ) + swapMap + val result = compute(stateMap, gates, newSwapMap) + + newSwapMap to result.bitDistance(wantedResult) + } + }.sortedBy { it.second } + .toMutableList() - sentry.value.forEach { a -> - sentry.value.forEach { b -> - val swappedResult = computeState(reducedState, reducedGates, mapOf(a to b, b to a)) - logger.info { "swapping $a $b result $swappedResult" } - if (swappedResult[sentry.key.first] == sentry.key.second) { - logger.info("swap $a $b") + do { + runBlocking(Dispatchers.IO) { + val chunk = (1..3).map { _ -> swapErrorList.removeFirst() } + val results = + chunk.map { + async { + if (it.second == 0) { + logger.info { "found ${it.first}" } + return@async it.first.keys.sorted() + } + if (i > 1) { + findSwaps( + wantedResult, + stateMap, + gates, + it.first, + i - 1, + )?.let { s -> return@async s } + } + null + } } - } + results.awaitAll().filterNotNull() + }.let { + if (it.isNotEmpty()) return it.first() } - } + } while (swapErrorList.isNotEmpty()) -// logger.info { "Initial suspects: $suspects" } -// do { -// var newSuspects = mutableSetOf() -// -// suspects.forEach { suspect -> -// val suspectGate = -// gateLines.firstOrNull { it.target == suspect } -// ?: return@forEach -// if (suspectGate.inputA !in suspects) newSuspects.add(suspectGate.inputA) -// if (suspectGate.inputB !in suspects) newSuspects.add(suspectGate.inputB) +// swapErrorList.forEach { +// if (it.second == 0) { +// logger.info { "found ${it.first}" } +// return it.first.keys.sorted() // } -// -// suspects.addAll(newSuspects) -// } while (newSuspects.isEmpty()) -// -// logger.info { "Found suspects: $suspects" } -// -// val swapPool = -// suspects -// .allSubsets() -// .filter { it.size == swaps * 2 } -// .firstNotNullOf { swapMembers -> -// swapMembers -// .allOrders() -// .firstOrNull { -// val swapMap = mutableMapOf() -// -// it.chunked(2).forEach { (a, b) -> -// swapMap[a] = b -// swapMap[b] = a -// } -// -// wantedResult == -// compute( -// stateMap, -// gateLines, -// swapMap, -// ) -// } -// } -// -// return swapPool -// .sorted() -// .joinToString(",") - return "" +// if (i > 1) { +// findSwaps( +// wantedResult, +// stateMap, +// gates, +// it.first, +// i - 1, +// )?.let { s -> return s } +// } +// null +// } + return null } private fun parseStateMap(stateLines: List): MutableMap { @@ -233,25 +231,3 @@ private fun parseGates(lines: List) = target, ) } - -private fun getNameWhitelist( - target: String, - gates: List, -): Set { - var suspects = mutableSetOf(target) - - do { - var newSuspects = mutableSetOf() - - suspects.forEach { suspect -> - val suspectGate = - gates.firstOrNull { it.target == suspect } - ?: return@forEach - if (suspectGate.inputA !in suspects) newSuspects.add(suspectGate.inputA) - if (suspectGate.inputB !in suspects) newSuspects.add(suspectGate.inputB) - } - - suspects.addAll(newSuspects) - } while (newSuspects.isNotEmpty()) - return suspects -} diff --git a/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt b/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt index 63e2fcc..0823175 100644 --- a/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt +++ b/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt @@ -19,4 +19,14 @@ class GetBitTest : 1024L.getBit(0) shouldBe 0 } } + + "bitDistance" { + assertSoftly { + 1L.bitDistance(1L) shouldBe 0 + 2L.bitDistance(0L) shouldBe 1 + 2L.bitDistance(1L) shouldBe 2 + 1024L.bitDistance(1L) shouldBe 2 + 1L.bitDistance(1024L) shouldBe 2 + } + } }) From bc45951bde0812a13e9bc840e3aff5ab46f96912 Mon Sep 17 00:00:00 2001 From: Petr Sykora Date: Sun, 27 Jul 2025 23:04:36 +0200 Subject: [PATCH 3/3] feat: something something --- .tool-versions | 2 +- src/main/kotlin/cz/glubo/adventofcode/Main.kt | 4 ++ .../glubo/adventofcode/y2024/day25/Day25.kt | 70 +++++++++++++++++++ .../adventofcode/y2024/day25/Day25Test.kt | 69 ++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/cz/glubo/adventofcode/y2024/day25/Day25.kt create mode 100644 src/test/kotlin/cz/glubo/adventofcode/y2024/day25/Day25Test.kt diff --git a/.tool-versions b/.tool-versions index ef05efb..d5eb9e9 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -java temurin-21.0.2+13.0.LTS +java 21 diff --git a/src/main/kotlin/cz/glubo/adventofcode/Main.kt b/src/main/kotlin/cz/glubo/adventofcode/Main.kt index 74001c2..b9db554 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/Main.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/Main.kt @@ -78,6 +78,8 @@ import cz.glubo.adventofcode.y2024.day23.y2024day23part1 import cz.glubo.adventofcode.y2024.day23.y2024day23part2 import cz.glubo.adventofcode.y2024.day24.y2024day24part1 import cz.glubo.adventofcode.y2024.day24.y2024day24part2 +import cz.glubo.adventofcode.y2024.day25.y2024day25part1 +import cz.glubo.adventofcode.y2024.day25.y2024day25part2 import cz.glubo.adventofcode.y2024.day3.y2024day3part1 import cz.glubo.adventofcode.y2024.day3.y2024day3part2 import cz.glubo.adventofcode.y2024.day4.y2024day4part1 @@ -291,6 +293,8 @@ fun main(args: Array) { "2024day23p2" to InputToStringCommand { y2024day23part2(it) }, "2024day24p1" to InputToLongCommand { y2024day24part1(it) }, "2024day24p2" to InputToStringCommand { y2024day24part2(it, 4) }, + "2024day25p1" to InputToLongCommand { y2024day25part1(it) }, + "2024day25p2" to InputToLongCommand { y2024day25part2(it) }, ) val cmd = CommandLine(MyHelpCommand()) diff --git a/src/main/kotlin/cz/glubo/adventofcode/y2024/day25/Day25.kt b/src/main/kotlin/cz/glubo/adventofcode/y2024/day25/Day25.kt new file mode 100644 index 0000000..5be6b00 --- /dev/null +++ b/src/main/kotlin/cz/glubo/adventofcode/y2024/day25/Day25.kt @@ -0,0 +1,70 @@ +package cz.glubo.adventofcode.y2024.day25 + +import cz.glubo.adventofcode.utils.input.Input +import io.klogging.noCoLogger +import kotlinx.coroutines.flow.toList + +val logger = noCoLogger({}.javaClass.toString()) + +fun parseItemAndPush( + buf: MutableList, + items: MutableList>, +): Int { + val item = + buf.first().indices.map { i -> + buf.sumOf { + val height = if (it[i] == '#') 1 else 0 + height + } - 1 + } + + items.addLast(item) + + return buf.size - 1 +} + +fun parseLockOrKeyAndPushIt( + buf: MutableList, + keys: MutableList>, + locks: MutableList>, +): Int = + when { + buf.isEmpty() -> 0 + buf.first().all { it == '#' } -> parseItemAndPush(buf, keys) + buf.last().all { it == '#' } -> parseItemAndPush(buf, locks) + else -> error("Not a key or a lock: $buf") + } + +suspend fun y2024day25part1(input: Input): Long { + logger.info("year 2024 day 25 part 1") + val keys = mutableListOf>() + val locks = mutableListOf>() + var buf = mutableListOf() + var height: Int = 0 + input + .lineFlow() + .toList() + .forEach { line -> + if (line.isNotBlank()) { + buf += line + } else { + height = parseLockOrKeyAndPushIt(buf, keys, locks) + buf.clear() + } + } + parseLockOrKeyAndPushIt(buf, keys, locks) + + return keys + .sumOf { key -> + locks.count { lock -> + key.indices.all { i -> + (key[i] + lock[i]) < height + } + } + }.toLong() +} + +suspend fun y2024day25part2(input: Input): Long { + logger.info("year 2024 day 25 part 2") + return 0 +} diff --git a/src/test/kotlin/cz/glubo/adventofcode/y2024/day25/Day25Test.kt b/src/test/kotlin/cz/glubo/adventofcode/y2024/day25/Day25Test.kt new file mode 100644 index 0000000..9c2538d --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/y2024/day25/Day25Test.kt @@ -0,0 +1,69 @@ +package cz.glubo.adventofcode.y2024.day25 + +import cz.glubo.adventofcode.utils.input.TestInput +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +/** + * https://adventofcode.com/2024/day/25 + */ +class Day25Test : + StringSpec({ + + "day25 example part 1 matches" { + y2024day25part1( + TestInput( + """ + ##### + .#### + .#### + .#### + .#.#. + .#... + ..... + + ##### + ##.## + .#.## + ...## + ...#. + ...#. + ..... + + ..... + #.... + #.... + #...# + #.#.# + #.### + ##### + + ..... + ..... + #.#.. + ###.. + ###.# + ###.# + ##### + + ..... + ..... + ..... + #.... + #.#.. + #.#.# + ##### + """.trimIndent(), + ), + ) shouldBe 3 + } + + "day25 example part 2 matches" { + y2024day25part2( + TestInput( + """ + """.trimIndent(), + ), + ) shouldBe 0 + } + })