diff --git a/src/main/kotlin/de/linkel/aoc/Day20.kt b/src/main/kotlin/de/linkel/aoc/Day20.kt index c5b6ad3..831a40b 100644 --- a/src/main/kotlin/de/linkel/aoc/Day20.kt +++ b/src/main/kotlin/de/linkel/aoc/Day20.kt @@ -2,13 +2,222 @@ package de.linkel.aoc import de.linkel.aoc.base.AbstractLinesAdventDay import de.linkel.aoc.base.QuizPart +import de.linkel.aoc.utils.lcm import jakarta.inject.Singleton @Singleton -class Day20: AbstractLinesAdventDay() { +class Day20: AbstractLinesAdventDay() { override val day = 20 - override fun process(part: QuizPart, lines: Sequence): Int { - return 0 + override fun process(part: QuizPart, lines: Sequence): Long { + val parsedModules = lines + .map { line -> + val spec = line.substringBefore(" ") + val next = line.substringAfter(" -> ").split(",").map(String::trim) + if (spec == "broadcaster") { + Broadcaster(spec, next) + } else when(spec[0]) { + '%' -> FlipFlop(spec.substring(1), next) + '&' -> Conjunction(spec.substring(1), next) + else -> throw Exception("unknown module spec $spec") + } + } + .toList() + val button = Button("button", listOf("broadcaster")) + val rx = Rx("rx") + val modules = parsedModules + button + rx + val nops = (modules.flatMap { it.next }.distinct().toSet() - modules.map { it.name }) + .map { Nop(it) } + val dispatcher = CommunicationDispatcher(modules + nops) + dispatcher.init() + + if (part == QuizPart.A) { + repeat(1000) { + button.press() + while (dispatcher.busy) { + dispatcher.tick() + } + } + return dispatcher.lowPulses * dispatcher.highPulses + } else { + return if (modules.any { "rx" in it.next }) { + val feeder = modules.first { it.name == rx.inputs.first() } as Conjunction + var buttonPresses = 0L + val cycles = feeder.lastSignals + .mapValues { + 0L + } + .toMutableMap() + while (true) { + rx.reset() + buttonPresses++ + button.press() + while (dispatcher.busy) { + dispatcher.tick() + } + feeder.gotHigh + .forEach { + if (it.value && cycles[it.key] == 0L) { + cycles[it.key] = buttonPresses + } + } + if (cycles.all { it.value > 0L }) { + break + } + } + return cycles.values.fold(1L) { a, b -> lcm(a, b) } + } else 0L + } + + } + + data class Message( + val sender: String, + val receivers: List, + val pulse: Boolean + ) + class CommunicationDispatcher( + modules: Collection + ) { + private val modules = modules.associateBy { it.name } + + private val queue = mutableListOf() + private var lowPulseCounter = 0L + private var highPulseCounter = 0L + val lowPulses get(): Long = lowPulseCounter + val highPulses get(): Long = highPulseCounter + val busy get() = queue.isNotEmpty() + fun init() { + modules.values + .onEach { it.dispatcher = this } + .flatMap { module -> module.next.map { module.name to it } } + .groupBy { it.second } + .mapValues { entry -> entry.value.map { it.first } } + .forEach { (module, prev) -> + modules[module]?.wireUp(prev) ?: throw Exception("module $module not found") + } + } + fun dispatch(sender: String, pulse: Boolean, receivers: List) { + queue.add(Message(sender, receivers, pulse)) + } + fun tick() { + if (queue.isNotEmpty()) { + queue.removeAt(0) + .let { message -> + message.receivers + .map { modules[it]!! } +// .onEach { println("${message.sender} -${if (message.pulse) "high" else "low"}-> ${it.name}") } + .onEach { if (message.pulse) highPulseCounter++ else lowPulseCounter++ } + .forEach { it.receive(message.sender, message.pulse) } + } + } + } + } + + abstract class Module( + val name: String, + val next: List + ) { + var dispatcher: CommunicationDispatcher? = null + protected fun send(pulse: Boolean) { + dispatcher?.dispatch(name, pulse, next) + } + open fun wireUp(prev: List) { + } + abstract fun receive(sender: String, pulse: Boolean) + } + + class FlipFlop( + name: String, + next: List + ): Module(name, next) { + var state = false + override fun receive(sender: String, pulse: Boolean) { + if (!pulse) { + state = !state + send(state) + } + } + + override fun toString(): String = "FlipFlop $name" + } + + class Conjunction( + name: String, + next: List + ): Module(name, next) { + private var state = mutableMapOf() + val gotHigh = mutableMapOf() + val lastSignals get(): Map = state + override fun wireUp(prev: List) { + prev.forEach { state[it] = false } + prev.forEach { gotHigh[it] = false } + } + override fun receive(sender: String, pulse: Boolean) { + state[sender] = pulse + if (pulse) { + gotHigh[sender] = true + } + send(state.values.any { !it }) + } + + override fun toString(): String = "Conjunction $name" + } + + class Broadcaster( + name: String, + next: List + ): Module(name, next) { + override fun receive(sender: String, pulse: Boolean) { + send(pulse) + } + + override fun toString(): String = "Broadcaster $name" + } + class Rx( + name: String + ): Module(name, emptyList()) { + private val states = mutableListOf() + val lastSignals get(): List = states + + private var prev = emptyList() + val inputs get(): List = prev + + fun reset() { + states.clear() + } + + override fun wireUp(prev: List) { + this.prev = prev + } + + override fun receive(sender: String, pulse: Boolean) { + states.add(pulse) + } + + override fun toString(): String = "Rx $name" + } + + class Nop( + name: String + ): Module(name, emptyList()) { + override fun receive(sender: String, pulse: Boolean) { + } + + override fun toString(): String = "Nop $name" + } + + class Button( + name: String, + next: List + ): Module(name, next) { + fun press() { + if (dispatcher?.busy == false) + send(false) + } + override fun receive(sender: String, pulse: Boolean) { + } + + override fun toString(): String = "Button $name" } } diff --git a/src/test/kotlin/de/linkel/aoc/Day20Test.kt b/src/test/kotlin/de/linkel/aoc/Day20Test.kt index b691167..3f0c7ea 100644 --- a/src/test/kotlin/de/linkel/aoc/Day20Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day20Test.kt @@ -1,13 +1,26 @@ package de.linkel.aoc -class Day20Test: AbstractDayTest() { +class Day20Test: AbstractDayTest() { override val exampleA = """ +broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a """.trimIndent() - override val exampleSolutionA = 0 - override val solutionA = 0 + val exampleA2 = """ +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output + """.trimIndent() + + override val exampleSolutionA = 32000000L + override val solutionA = 832957356L - override val exampleSolutionB = 0 - override val solutionB = 0 + override val exampleSolutionB = 0L + override val solutionB = 240162699605221L override val implementation = Day20() } diff --git a/src/test/kotlin/de/linkel/aoc/Day21Test.kt b/src/test/kotlin/de/linkel/aoc/Day21Test.kt index e8f3021..37cd807 100644 --- a/src/test/kotlin/de/linkel/aoc/Day21Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day21Test.kt @@ -9,5 +9,5 @@ class Day21Test: AbstractDayTest() { override val exampleSolutionB = 0 override val solutionB = 0 - override val implementation = Day20() + override val implementation = Day21() } diff --git a/src/test/kotlin/de/linkel/aoc/Day22Test.kt b/src/test/kotlin/de/linkel/aoc/Day22Test.kt index 44b23b6..44f2dd1 100644 --- a/src/test/kotlin/de/linkel/aoc/Day22Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day22Test.kt @@ -9,5 +9,5 @@ class Day22Test: AbstractDayTest() { override val exampleSolutionB = 0 override val solutionB = 0 - override val implementation = Day20() + override val implementation = Day22() } diff --git a/src/test/kotlin/de/linkel/aoc/Day23Test.kt b/src/test/kotlin/de/linkel/aoc/Day23Test.kt index 666d4ea..daed4df 100644 --- a/src/test/kotlin/de/linkel/aoc/Day23Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day23Test.kt @@ -9,5 +9,5 @@ class Day23Test: AbstractDayTest() { override val exampleSolutionB = 0 override val solutionB = 0 - override val implementation = Day20() + override val implementation = Day23() } diff --git a/src/test/kotlin/de/linkel/aoc/Day24Test.kt b/src/test/kotlin/de/linkel/aoc/Day24Test.kt index 70dff98..2ec83fa 100644 --- a/src/test/kotlin/de/linkel/aoc/Day24Test.kt +++ b/src/test/kotlin/de/linkel/aoc/Day24Test.kt @@ -9,5 +9,5 @@ class Day24Test: AbstractDayTest() { override val exampleSolutionB = 0 override val solutionB = 0 - override val implementation = Day20() + override val implementation = Day24() }