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/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/Main.kt b/src/main/kotlin/cz/glubo/adventofcode/Main.kt index 8b5fee3..b9db554 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/Main.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/Main.kt @@ -76,6 +76,10 @@ 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.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 @@ -287,6 +291,10 @@ 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) }, + "2024day25p1" to InputToLongCommand { y2024day25part1(it) }, + "2024day25p2" to InputToLongCommand { y2024day25part2(it) }, ) 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..e821c92 100644 --- a/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt +++ b/src/main/kotlin/cz/glubo/adventofcode/utils/Utils.kt @@ -293,3 +293,35 @@ 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) + +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 new file mode 100644 index 0000000..736a3a0 --- /dev/null +++ b/src/main/kotlin/cz/glubo/adventofcode/y2024/day24/Day24.kt @@ -0,0 +1,233 @@ +package cz.glubo.adventofcode.y2024.day24 + +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 + +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 a = + findSwaps( + wantedResult, + stateMap, + gates, + emptyMap(), + 4, + ) + + return a!!.joinToString(",") +} + +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() + + 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()) + +// swapErrorList.forEach { +// if (it.second == 0) { +// logger.info { "found ${it.first}" } +// return it.first.keys.sorted() +// } +// if (i > 1) { +// findSwaps( +// wantedResult, +// stateMap, +// gates, +// it.first, +// i - 1, +// )?.let { s -> return s } +// } +// null +// } + return null +} + +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, + ) + } 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/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..0823175 --- /dev/null +++ b/src/test/kotlin/cz/glubo/adventofcode/utils/GetBitTest.kt @@ -0,0 +1,32 @@ +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 + } + } + + "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 + } + } + }) 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 +// } + }) 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 + } + })