Skip to content

Commit

Permalink
Day 20: Pulse Propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
ephemient committed Dec 20, 2023
1 parent 4ae14b5 commit f68184d
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ Development occurs in language-specific directories:
|[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day17.kt)|[day17.py](py/aoc2023/day17.py)|[day17.rs](rs/src/day17.rs)|
|[Day18.hs](hs/src/Day18.hs)|[Day18.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day18.kt)|[day18.py](py/aoc2023/day18.py)|[day18.rs](rs/src/day18.rs)|
|[Day19.hs](hs/src/Day19.hs)|[Day19.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day19.kt)|[day19.py](py/aoc2023/day19.py)|[day19.rs](rs/src/day19.rs)|
|[Day20.hs](hs/src/Day20.hs)||||
|[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day20.kt)|||
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.github.ephemient.aoc2023.exe

import com.github.ephemient.aoc2023.Day20
import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.Scope
import kotlinx.benchmark.Setup
import kotlinx.benchmark.State

@State(Scope.Benchmark)
class Day20Bench {
private lateinit var input: String

@Setup
fun setup() {
input = getDayInput(20)
}

@Benchmark
fun part1() = Day20(input).part1()

@Benchmark
fun part2() = Day20(input).part2()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.github.ephemient.aoc2023

class Day20(input: String) {
private val machines: Map<String, Pair<Set<String>, Type?>>
private val dependencies: Map<String, Set<String>>

init {
val machines = mutableMapOf<String, Pair<Set<String>, Type?>>()
val dependencies = mutableMapOf<String, MutableSet<String>>()

for (line in input.lines()) {
val (lhs, rhs) = line.split(" -> ", limit = 2).takeIf { it.size == 2 } ?: continue
val (type, key) = when {
lhs.startsWith('%') -> Type.FlipFlop to lhs.drop(1)
lhs.startsWith('&') -> Type.Conjunction to lhs.drop(1)
else -> null to lhs
}
val dsts = rhs.split(", ").toSet()
machines[key] = dsts to type
for (dst in dsts) dependencies.getOrPut(dst) { mutableSetOf() }.add(key)
}

this.machines = machines
this.dependencies = dependencies
}

fun part1(): Int {
val state = machines.mapValues { (key, value) ->
value.second.createState(dependencies[key] ?: emptySet())
}
var x = 0
var y = 0
repeat(1000) {
val queue = ArrayDeque<Triple<String, String, Boolean>>()
queue.add(Triple("button", "broadcaster", false))
@Suppress("LoopWithTooManyJumpStatements")
while (true) {
val (src, key, value) = queue.removeFirstOrNull() ?: break
if (value) x++ else y++
val newValue = state[key]?.onPulse(src, value) ?: continue
machines.getValue(key).first.mapTo(queue) { Triple(key, it, newValue) }
}
}
return x * y
}

@Suppress("CyclomaticComplexMethod", "NestedBlockDepth", "ReturnCount")
fun part2(): Long? {
val conjunction = dependencies["rx"]?.singleOrNull()
?.takeIf { machines[it]?.second == Type.Conjunction }
?: return null
val subsets = (dependencies[conjunction] ?: emptySet()).associateWith { dst ->
buildSet {
val stack = mutableListOf(dst)
@Suppress("LoopWithTooManyJumpStatements")
while (true) {
val key = stack.removeLastOrNull() ?: break
if (!add(key)) continue
dependencies[key]?.let { stack.addAll(it) }
}
}
}
subsets.values.toList().let { values ->
for (i in values.indices) {
for (j in i + 1..values.lastIndex) {
if (values[i].intersect(values[j]).singleOrNull() != "broadcaster") {
return null
}
}
}
}
return subsets.entries.fold(1) { acc: Long, (dst, subset) ->
val state = machines.filterKeys { it in subset }.mapValues { (key, value) ->
value.second.createState(dependencies[key] ?: emptySet())
}
val seen = mutableMapOf<Map<String, Boolean?>, Int>()
var hadOutput = false
while (true) {
var hasOutput = false
val queue = ArrayDeque<Triple<String, String, Boolean>>()
queue.add(Triple("button", "broadcaster", false))
@Suppress("LoopWithTooManyJumpStatements")
while (true) {
val (src, key, value) = queue.removeFirstOrNull() ?: break
val newValue = state[key]?.onPulse(src, value) ?: continue
if (newValue && key == dst) hasOutput = true
machines.getValue(key).first
.filter { it in subset }
.mapTo(queue) { Triple(key, it, newValue) }
}
val snapshot = state.mapValues { it.value.value }
if (snapshot in seen) {
if (!hadOutput) return null
val i = seen.getValue(snapshot)
if (i != 0) return null
break
}
seen[snapshot] = seen.size
if (hadOutput) return null
hadOutput = hasOutput
}
lcm(acc, seen.size.toLong())
}
}

private sealed interface State {
object Default : State {
override val value: Boolean?
get() = null

override fun onPulse(key: String, value: Boolean): Boolean = value
}

class FlipFlop : State {
override var value = false
private set

override fun onPulse(key: String, value: Boolean): Boolean? = if (value) {
null
} else {
!this.value
}?.also { this.value = it }
}

class Conjunction(keys: Set<String>) : State {
private val state = keys.associateWithTo(mutableMapOf()) { false }

override val value: Boolean
get() = !state.values.all { it }

override fun onPulse(key: String, value: Boolean): Boolean {
state[key] = value
return this.value
}
}

val value: Boolean?

fun onPulse(key: String, value: Boolean): Boolean?
}

private enum class Type {
FlipFlop, Conjunction,
}

companion object {
private fun Type?.createState(keys: Set<String>): State = when (this) {
null -> State.Default
Type.FlipFlop -> State.FlipFlop()
Type.Conjunction -> State.Conjunction(keys)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ val days = listOf(
Day(17, ::Day17, Day17::part1, Day17::part2),
Day(18, ::Day18, Day18::part1, Day18::part2),
Day(19, ::Day19, Day19::part1, Day19::part2),
Day(20, ::Day20, Day20::part1, Day20::part2),
)

data class Day(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.ephemient.aoc2023

import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals

class Day20Test {
@Test
fun part1() {
assertEquals(32000000, Day20(example1).part1())
assertEquals(11687500, Day20(example2).part1())
}

@Test
@Ignore
fun part2() {
assertEquals(0, Day20("").part2())
}

companion object {
private val example1 =
"""
|broadcaster -> a, b, c
|%a -> b
|%b -> c
|%c -> inv
|&inv -> a
|""".trimMargin()
private val example2 =
"""
|broadcaster -> a
|%a -> inv, con
|&inv -> b
|%b -> con
|&con -> output
|""".trimMargin()
}
}

0 comments on commit f68184d

Please sign in to comment.