Skip to content

Commit

Permalink
2023 - Day 20 - part 1 and 2 - phuuu
Browse files Browse the repository at this point in the history
  • Loading branch information
fmmr committed Dec 21, 2023
1 parent c788771 commit dc0a6b5
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 20 deletions.
7 changes: 7 additions & 0 deletions src/main/kotlin/no/rodland/advent/Misc.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import no.rodland.advent.Pos
import java.math.BigInteger


fun String.readFile(): List<String> {
Expand All @@ -20,6 +21,12 @@ fun String.readFileAsString(): String {
}
}

fun lcm(n1: BigInteger, n2: BigInteger): BigInteger {
// https://no.wikipedia.org/wiki/Minste_felles_multiplum
// lcm = (n1 * n2) / gcd
return (n1 * n2) / n1.gcd(n2)
}

fun String.readFileAsInt(): List<Int> {
return readFile().map { it.toInt() }
}
Expand Down
155 changes: 143 additions & 12 deletions src/main/kotlin/no/rodland/advent_2023/Day20.kt
Original file line number Diff line number Diff line change
@@ -1,41 +1,172 @@
package no.rodland.advent_2023

import lcm
import no.rodland.advent.Day
import java.math.BigInteger

// template generated: 20/12/2023
// Fredrik Rødland 2023

class Day20(val input: List<String>) : Day<Long, Long, Map<String, Day20.Mod>> {

private val parsed = input.parse()
val parsed = input.parse()

override fun partOne(): Long {
return 2
parsed.values.forEach { it.reset() }
repeat(1000) { sendPulse("button", Pulse.LOW) }
val high = parsed.values.sumOf { it.high }
val low = parsed.values.sumOf { it.low }
return high * low.toLong()
}


override fun partTwo(): Long {
return 2
parsed.values.forEach { it.reset() }
// rx gets a low pulse when nextToLast sends a low pulse
// nextToLast sends a low pulse when all beforeNextToLast are high
val nextToLast = parsed.values.single { it.destinations.contains("rx") }
val beforeNextToLast = parsed.values.filter { it.destinations.contains(nextToLast.id) }.map { it.id }
val periods = beforeNextToLast.associateWith { -1L }.toMutableMap()

var count = 0L
while (true) {
val result = sendPulse("button", Pulse.LOW, periods, ++count)
if (result != 0L) {
return result
}
}
}

private fun checkPart2(source: String, periods: MutableMap<String, Long>, pulse: Pulse, count: Long): Boolean {
if (source in periods) {
if (pulse == Pulse.HIGH && periods[source]!! == -1L) {
periods[source] = count
}
if (periods.values.all { it != -1L }) {
println("all found: $periods")
return true
}
}
return false
}


private fun sendPulse(mod: String, pulse: Pulse, periods: MutableMap<String, Long> = mutableMapOf(), count: Long = 0L): Long {
val deque = ArrayDeque<QueueElement>()
deque.addAll(parsed[mod]!!.pulse("button", pulse).map { QueueElement(mod, it.second, it.first) })
while (deque.isNotEmpty()) {
deque.removeFirst().let { (source, pulse, destination) ->
if (checkPart2(source, periods, pulse, count)) return periods.values.map { it.toBigInteger() }.fold(BigInteger.ONE) { acc, i -> lcm(acc, i) }.toLong()
val dest = parsed[destination]!!
val addToQueue = dest.pulseWithCount(source, pulse)
deque.addAll(addToQueue.map { QueueElement(destination, it.second, it.first) })
}
}
return 0L
}


data class QueueElement(val source: String, val pulse: Pulse, val destination: String)
enum class Pulse { HIGH, LOW }

sealed class Mod(val id: String, val destinations: List<String>) {
var high = 0
var low = 0
override fun toString(): String {
return "$id, $low, $high"
}

fun reset() {
high = 0
low = 0
resetSub()
}

fun pulseWithCount(source: String, pulse: Pulse): List<Pair<String, Pulse>> {
if (pulse == Pulse.HIGH) {
high++
} else {
low++
}
return pulse(source, pulse)
}

abstract fun resetSub()
abstract fun pulse(source: String, pulse: Pulse): List<Pair<String, Pulse>>
}

class FlipFlop(id: String, destinations: List<String>) : Mod(id, destinations) {
private var memory = false
override fun resetSub() {
memory = false
}

override fun pulse(source: String, pulse: Pulse): List<Pair<String, Pulse>> {
return if (pulse == Pulse.LOW) {
val pulseToSend = if (!memory) Pulse.HIGH else Pulse.LOW
memory = !memory
destinations.map { it to pulseToSend }
} else {
emptyList()
}

}
}

data class Conjunction(val iD: String, val dest: List<String>, val sources: List<String> = emptyList()) : Mod(iD, dest) {
private val memory = sources.associateWith { Pulse.LOW }.toMutableMap()
override fun resetSub() {
memory.replaceAll { _, _ -> Pulse.LOW }
}

override fun pulse(source: String, pulse: Pulse): List<Pair<String, Pulse>> {
memory[source] = pulse
val pulseToSend = if (memory.values.all { it == Pulse.HIGH }) {
Pulse.LOW
} else {
Pulse.HIGH
}
return destinations.map { it to pulseToSend }
}
}

class Forwarder(iD: String, destinations: List<String>) : Mod(iD, destinations) {
override fun resetSub() {}

override fun pulse(source: String, pulse: Pulse): List<Pair<String, Pulse>> = destinations.map { it to pulse }
}

class NA(iD: String) : Mod(iD, emptyList()) {
override fun resetSub() {}
override fun pulse(source: String, pulse: Pulse): List<Pair<String, Pulse>> = emptyList()
}

sealed class Mod(val id: String, val destinations: List<String>)
class FlipFlop(id: String, destinations: List<String>) : Mod(id, destinations)
class Broadcast(destinations: List<String>) : Mod("broadcaster", destinations)
class Conjunction(id: String, destinations: List<String>) : Mod(id, destinations)

override fun List<String>.parse(): Map<String, Mod> {
return associate { line ->
println(line)
val mods = map { line ->
val (id, list) = line.split(" -> ")
val destinations = list.split(";", " ").filterNot { it.isEmpty() }
val destinations = list.split(";", " ", ",").filterNot { it.isEmpty() }

val mod = when {
id.startsWith("&") -> Conjunction(id.drop(1), destinations)
id.startsWith("%") -> FlipFlop(id.drop(1), destinations)
id == "broadcaster" -> Broadcast(destinations)
id == "broadcaster" -> Forwarder("broadcaster", destinations)
else -> error("unknown mod: $id")
}
mod.id to mod
mod
}
val na = mods.flatMap { it.destinations }.distinct().filterNot { it in mods.map { it.id } }.map {
NA(it)
}
return (mods + na + Forwarder("button", listOf("broadcaster"))).map { mod ->
if (mod is Conjunction) {
mod.copy(sources = mods.filter { mod.id in it.destinations }.map { it.id })
} else {
mod
}
}.associateBy { it.id }
}


override val day = "20".toInt()
}
19 changes: 11 additions & 8 deletions src/test/kotlin/no/rodland/advent_2023/Day20Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ internal class Day20Test {
private val test20 = "2023/input_20_test.txt".readFile()
private val test20_2 = "2023/input_20_test_2.txt".readFile()

private val resultTestOne = 2L
private val resultTestOne = 32000000L
private val resultTestTwo = 2L
private val resultTestTwo_2 = 2L
private val resultOne = 2L
private val resultTwo = 2L
private val resultTestTwo_2 = 11687500L
private val resultOne = 819397964L
private val resultTwo = 252667369442479L

val test = defaultTestSuiteParseOnInit(
Day20(data20),
Expand All @@ -31,6 +31,8 @@ internal class Day20Test {
resultTwo,
{ Day20(data20) },
{ Day20(test20) },
numTestPart1 = 20,
numTestPart2 = 4
)

@Nested
Expand Down Expand Up @@ -62,6 +64,7 @@ internal class Day20Test {
fun `20,1,test`() {
report(test.testPart1)
}

@Test
fun `20,1,test,2`() {
report(test.testPart1.copy(function = { Day20(test20_2).partOne() }, expected = resultTestTwo_2))
Expand All @@ -75,10 +78,10 @@ internal class Day20Test {

@Nested
inner class `Part 2` {
@Test
fun `20,2,test`() {
report(test.testPart2)
}
// @Test
// fun `20,2,test`() {
// report(test.testPart2)
// }

@Test
fun `20,2,live,1`() {
Expand Down

0 comments on commit dc0a6b5

Please sign in to comment.