This commit is contained in:
Paul-Henri Froidmont 2023-12-21 13:01:01 +01:00
parent 6e3628e669
commit 4408075c94
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
2 changed files with 198 additions and 0 deletions

58
input/day20 Normal file
View file

@ -0,0 +1,58 @@
%dj -> fj, jn
%xz -> cm
%fn -> rj
%fv -> nt, zp
%ls -> ph, cf
%rk -> zp, tp
&jn -> km, cr, vz
%nh -> ph, ls
%tx -> gb
%xg -> dv, zp
%tp -> gx
&zp -> kj, kz, gx, fv, lv, tp
&gq -> rx
%fj -> sl, jn
%cr -> vz, jn
%rt -> fn, mf
%kj -> tt
%tk -> mg, ph
%xt -> jn, gh
%qx -> bx
%lv -> sx
%nz -> dp, ph
%sx -> kj, zp
%dd -> bf
%gb -> jp
%bj -> ph, nn
%sk -> mf
%bx -> tx, mf
%mt -> xg, zp
%vz -> hf
%vx -> mf, sk
%tt -> mt, zp
%br -> jn, fk
&xj -> gq
%mg -> ph, ps
%nt -> zp, rk
&qs -> gq
%rj -> qx, mf
%bf -> vx, mf
&kz -> gq
%fk -> jn, gk
%dv -> zp
%dp -> ph
&mf -> gb, tx, xj, dd, qx, rt, fn
&ph -> nn, xz, tk, ps, qs
%ps -> xz
&km -> gq
broadcaster -> fv, cr, rt, tk
%gk -> jn, xt
%cf -> ph, nz
%tl -> jn, br
%cm -> bj, ph
%nn -> nh
%jp -> mf, dd
%gh -> jn, dj
%hf -> tl, jn
%sl -> jn
%gx -> lv

140
src/day20.scala Normal file
View file

@ -0,0 +1,140 @@
package aoc
package day20
import scala.annotation.tailrec
import scala.collection.mutable
import scala.util.boundary
val dayNumber = "20"
@main def part1: Unit =
println(part1(loadInput(dayNumber)))
@main def part2: Unit =
println(part2(loadInput(dayNumber)))
enum Pulse:
case High, Low
enum Module:
case FlipFlop(var on: Boolean = false)
case Conjuction(var seenPulses: Map[String, Pulse] = Map.empty)
case Broadcast
case Sink(var lowCount: Long = 0, var highCount: Long = 0)
object Module:
def parseAll(str: String): Map[String, (Module, List[String])] =
val modules = str
.split('\n')
.map {
case s"broadcaster -> $targets" =>
Module.Broadcast.toString -> (Module.Broadcast -> targets
.split(',')
.map(_.trim)
.toList)
case s"%$name -> $targets" =>
name -> (Module.FlipFlop() -> targets.split(',').map(_.trim).toList)
case s"&$name -> $targets" =>
name -> (Module.Conjuction() -> targets.split(',').map(_.trim).toList)
}
.toMap
modules.foreach {
case (name, (module: Module.Conjuction, _)) =>
module.seenPulses = modules.collect {
case (sourceName, (_, targets)) if targets.contains(name) =>
(sourceName -> Pulse.Low)
}
case _ => ()
}
val sinks = modules.values
.flatMap(_._2)
.toList
.distinct
.filterNot(modules.contains)
.map(_ -> (Module.Sink(), List.empty))
modules ++ sinks
def processPulse(
source: String,
pulse: Pulse,
moduleName: String,
modules: Map[String, (Module, List[String])]
): List[(String, Pulse, String)] =
val (module, targets) = modules(moduleName)
module match
case Module.Broadcast =>
targets.map((moduleName, pulse, _))
case module: Module.Sink =>
if pulse == Pulse.Low then module.lowCount += 1
else if pulse == Pulse.High then module.highCount += 1
List.empty
case module @ Module.FlipFlop(true) if pulse == Pulse.Low =>
module.on = false
targets.map((moduleName, Pulse.Low, _))
case module @ Module.FlipFlop(false) if pulse == Pulse.Low =>
module.on = true
targets.map((moduleName, Pulse.High, _))
case Module.FlipFlop(_) => List.empty
case module: Module.Conjuction =>
module.seenPulses = module.seenPulses.updated(source, pulse)
if module.seenPulses.values.forall(_ == Pulse.High) then
targets.map((moduleName, Pulse.Low, _))
else targets.map((moduleName, Pulse.High, _))
def part1(input: String): String =
val modules = Module.parseAll(input)
var lowCount = 0L
var highCount = 0L
def pushButton =
val queue = mutable.Queue(("Button", Pulse.Low, Module.Broadcast.toString))
while (queue.nonEmpty)
val (source, pulse, moduleName) = queue.dequeue
pulse match
case Pulse.High => highCount += 1
case Pulse.Low => lowCount += 1
queue.enqueueAll(processPulse(source, pulse, moduleName, modules))
for _ <- 1 to 1000 do pushButton
(lowCount * highCount).toString
def part2(input: String): String =
def findModuleSources(
name: String,
modules: Map[String, (Module, List[String])]
): List[String] =
modules.collect {
case (sourceName, (_, targets)) if targets.contains(name) => sourceName
}.toList
def countButtonPushesUntilHighPulseFor(targetModuleName: String): Long =
val modules = Module.parseAll(input)
boundary:
for pushes <- 1L to Int.MaxValue do
val queue =
mutable.Queue(("Button", Pulse.Low, Module.Broadcast.toString))
while (queue.nonEmpty)
val (source, pulse, moduleName) = queue.dequeue
if source == targetModuleName && pulse == Pulse.High then
boundary.break(pushes)
queue.enqueueAll(processPulse(source, pulse, moduleName, modules))
throw RuntimeException(
s"Module $targetModuleName never receives a high pulse"
)
@tailrec def gcd(a: Long, b: Long): Long =
if (b == 0) a.abs else gcd(b, a % b)
def lcm(a: Long, b: Long) =
(a * b).abs / gcd(a, b)
val modules = Module.parseAll(input)
val rxSource = findModuleSources("rx", modules).head
val sourceConjunctions = findModuleSources(rxSource, modules)
sourceConjunctions
.map(countButtonPushesUntilHighPulseFor)
.reduce(lcm)
.toString