Day 20
This commit is contained in:
parent
6e3628e669
commit
4408075c94
2 changed files with 198 additions and 0 deletions
58
input/day20
Normal file
58
input/day20
Normal 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
140
src/day20.scala
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue