Coverage Summary for Class: ModuleWithInput (day20p2)
Class |
Method, %
|
Line, %
|
ModuleWithInput$DefaultImpls |
100%
(2/2)
|
100%
(2/2)
|
Total |
100%
(2/2)
|
100%
(2/2)
|
package day20p2
import day20p2.Pulse.High
fun String.numberOfButtonPressesForFirstLowPulseToRx(): Long {
val modules = toModules()
return modules.numberOfButtonPressesForFirstLowPulseToRx()
}
fun String.pressButtonAndCountNumberOfPulses(): Long {
val modules = toModules()
return modules.pressButtonAndCountNumberOfPulses(1_000)
}
fun String.toModules(): Modules {
val builders = mutableMapOf(buttonModuleName to ModuleBuilder(buttonModuleName))
lineSequence().forEach { line ->
val (typedName, outputs) = line.split(" -> ").let { (name, outputs) -> name to outputs.split(", ").map { it.md } }
val (name, builder) = if (typedName.startsWith("%")) {
val n = typedName.substring(1).md
n to builders.moduleBuilder(n).asFlipFlop()
} else if (typedName.startsWith("&")) {
val n = typedName.substring(1).md
n to builders.moduleBuilder(n).asConjunction()
} else {
val n = typedName.md
n to builders.moduleBuilder(n)
}
builder.addOutputs(outputs)
outputs.forEach { output -> builders.moduleBuilder(output).addInput(name) }
}
return Modules(builders.mapValues { it.value.build() })
}
private fun MutableMap<ModuleName, ModuleBuilder>.moduleBuilder(name: ModuleName): ModuleBuilder =
computeIfAbsent(name) { ModuleBuilder(name) }
data class ModuleBuilder(val name: ModuleName) {
private val inputs = mutableSetOf<ModuleName>()
private val outputs = mutableListOf<ModuleName>()
private var builderFn: (ModuleBuilder.() -> Module) = when (name) {
buttonModuleName -> {
{
require(inputs.isEmpty())
require(outputs.isEmpty())
ButtonModule
}
}
broadcasterModuleName -> {
{
require(inputs.isEmpty())
require(outputs.isNotEmpty())
Broadcaster(outputs)
}
}
else -> {
{
require(inputs.isNotEmpty())
require(outputs.isEmpty())
UntypedModule(name, inputs)
}
}
}
fun asFlipFlop(): ModuleBuilder {
builderFn = {
require(inputs.isNotEmpty())
require(outputs.isNotEmpty())
FlipFlop(name, inputs, outputs)
}
return this
}
fun asConjunction(): ModuleBuilder {
builderFn = {
require(inputs.isNotEmpty())
require(outputs.isNotEmpty())
Conjunction(name, inputs, outputs)
}
return this
}
fun addInput(name: ModuleName): ModuleBuilder {
inputs.add(name)
return this
}
fun addOutputs(outputs: List<ModuleName>): ModuleBuilder {
this.outputs.addAll(outputs)
return this
}
fun build(): Module = builderFn()
}
data class Modules(val modules: Map<ModuleName, Module>) {
private val buttonModule = modules.getValue(buttonModuleName) as ButtonModule
fun pressButton(times: Int = 1): Pair<List<PulseMessage>, Modules> {
var current = this
var eventLog = emptyList<PulseMessage>()
repeat(times) {
val updatedModules = current.deepCopy()
eventLog = updatedModules.doPressButton()
current = updatedModules
}
return eventLog to current
}
fun pressButtonAndCountNumberOfPulses(times: Int): Long {
var current = this
var countHigh = 0L
var countLow = 0L
repeat(times) {
val (eventLog, updatedModules) = current.pressButton()
current = updatedModules
eventLog.forEach {
if (it.value == High) countHigh++ else countLow++
}
}
return countHigh * countLow
}
fun numberOfButtonPressesForFirstLowPulseToRx(): Long {
val rx = modules.getValue("rx".md)
require(rx is MultipleInputModule)
require(rx.inputs.size == 1)
val rxInputName = rx.inputs.single()
val rxInput = modules.getValue(rxInputName)
require(rxInput is Conjunction)
val inputsToTrack = rxInput.inputs.toMutableSet()
val numOfButtonPresses = mutableSetOf<Long>()
var countPresses = 0L
while (inputsToTrack.isNotEmpty()) {
countPresses++
doPressButton { message ->
if (message.value == High && message.to == rxInputName) {
inputsToTrack.remove(message.from)
numOfButtonPresses.add(countPresses)
}
}
}
return numOfButtonPresses.reduce { a, b -> a * b }
}
private fun doPressButton(onEvent: (PulseMessage) -> Unit = {}): List<PulseMessage> {
val messages = mutableListOf<PulseMessage>()
val eventLog = mutableListOf<PulseMessage>()
buttonModule.press(messages)
while (messages.isNotEmpty()) {
val message = messages.removeFirst()
eventLog += message
val toModule = modules.getValue(message.to)
require(toModule is ModuleWithInput)
toModule.send(message, messages)
onEvent(message)
}
return eventLog
}
private fun deepCopy(): Modules = Modules(modules.mapValues { it.value.deepCopy() })
}
data class PulseMessage(val from: ModuleName, val to: ModuleName, val value: Pulse) {
override fun toString(): String = "$from -${value.toString().lowercase()}-> $to"
}
data class ModuleName(val name: String) {
override fun toString(): String = name
}
sealed interface Module {
val name: ModuleName
fun toNamedPair(): Pair<ModuleName, Module> = name to this
fun deepCopy(): Module = this
}
sealed interface ModuleWithInput : Module {
fun send(message: PulseMessage, messageBus: MutableList<PulseMessage>)
}
sealed interface SingleInputModule : ModuleWithInput {
val input: ModuleName
}
sealed interface MultipleInputModule : ModuleWithInput {
val inputs: Set<ModuleName>
}
sealed interface SingleOutputModule : Module {
val output: ModuleName
}
sealed interface MultipleOutputModule : Module {
val outputs: List<ModuleName>
}
val String.md: ModuleName
get() = ModuleName(this)
fun moduleNames(vararg names: String) = names.map { it.md }
val outputModuleName = ModuleName("output")
val buttonModuleName = ModuleName("button")
val broadcasterModuleName = ModuleName("broadcaster")
data class UntypedModule(override val name: ModuleName, override val inputs: Set<ModuleName>) : MultipleInputModule {
override fun send(message: PulseMessage, messageBus: MutableList<PulseMessage>) {
// ignored
}
}
data object ButtonModule : SingleOutputModule {
fun press(messages: MutableList<PulseMessage>) {
messages += PulseMessage(buttonModuleName, broadcasterModuleName, Pulse.Low)
}
override val name = buttonModuleName
override val output = broadcasterModuleName
}
data class Broadcaster(override val outputs: List<ModuleName>) : SingleInputModule, MultipleOutputModule {
override val name = broadcasterModuleName
override val input = buttonModuleName
override fun send(message: PulseMessage, messageBus: MutableList<PulseMessage>) {
outputs.forEach { output -> messageBus += PulseMessage(broadcasterModuleName, output, message.value) }
}
}
data class FlipFlop(
override val name: ModuleName,
override val inputs: Set<ModuleName>,
override val outputs: List<ModuleName>,
var on: Boolean = false
) : MultipleInputModule, MultipleOutputModule {
override fun deepCopy(): FlipFlop = copy()
override fun send(message: PulseMessage, messageBus: MutableList<PulseMessage>) {
if (message.value == High) return
if (!on) {
on = true
outputs.forEach { output -> messageBus += PulseMessage(name, output, High) }
} else {
on = false
outputs.forEach { output -> messageBus += PulseMessage(name, output, Pulse.Low) }
}
}
}
data class Conjunction(
override val name: ModuleName,
override val inputs: Set<ModuleName>,
override val outputs: List<ModuleName>,
val lastPulses: MutableMap<ModuleName, Pulse> = inputs.associateWith { Pulse.Low }.toMutableMap()
) : MultipleInputModule, MultipleOutputModule {
override fun deepCopy(): Conjunction = copy(lastPulses = lastPulses.toMutableMap())
override fun send(message: PulseMessage, messageBus: MutableList<PulseMessage>) {
lastPulses[message.from] = message.value
val pulseToSend = if (lastPulses.values.all { it == High }) Pulse.Low else High
outputs.forEach { output -> messageBus += PulseMessage(name, output, pulseToSend) }
}
}
enum class Pulse {
Low, High
}