Day 20
This commit is contained in:
parent
74b1908d0c
commit
9269c6f2b2
145
Day20.scala
Normal file
145
Day20.scala
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package aoc.day20
|
||||||
|
|
||||||
|
import aoc._
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
enum Pulse:
|
||||||
|
case Low, High
|
||||||
|
|
||||||
|
def flip = Pulse.fromOrdinal(1 - ordinal)
|
||||||
|
import Pulse._
|
||||||
|
|
||||||
|
sealed abstract class Module(targets: Set[String]):
|
||||||
|
def handle(source: String, pulse: Pulse): (Module, Map[String, Pulse])
|
||||||
|
|
||||||
|
final def broadcast(p: Pulse) = targets.toIterator.map((_, p)).toMap
|
||||||
|
|
||||||
|
case class FlipFlop(state: Pulse, targets: Set[String]) extends Module(targets):
|
||||||
|
def handle(source: String, pulse: Pulse) = pulse match
|
||||||
|
case High => (this, Map.empty)
|
||||||
|
case Low =>
|
||||||
|
val nw = FlipFlop(state.flip, targets)
|
||||||
|
(nw, nw.broadcast(nw.state))
|
||||||
|
|
||||||
|
object FlipFlop:
|
||||||
|
def apply(targets: Set[String]) = new FlipFlop(Low, targets)
|
||||||
|
|
||||||
|
case class Conjunc(sources: Map[String, Pulse], targets: Set[String]) extends Module(targets):
|
||||||
|
override def handle(source: String, pulse: Pulse): (Module, Map[String, Pulse]) =
|
||||||
|
val nw = Conjunc(sources.updated(source, pulse), targets)
|
||||||
|
(nw, nw.broadcast(if nw.sources.values.forall(_ == High) then Low else High))
|
||||||
|
|
||||||
|
object Conjunc:
|
||||||
|
def apply(sources: Set[String], targets: Set[String]) = new Conjunc(sources.toIterator.map((_, Low)).toMap, targets)
|
||||||
|
|
||||||
|
case class Broadcast(targets: Set[String]) extends Module(targets):
|
||||||
|
def handle(source: String, pulse: Pulse): (Module, Map[String, Pulse]) = (this, broadcast(pulse))
|
||||||
|
|
||||||
|
enum ModuleTyp { case Broadcast, Flip, Conjunc }
|
||||||
|
object Parser extends CommonParser:
|
||||||
|
|
||||||
|
type Node = (ModuleTyp, String, Set[String])
|
||||||
|
|
||||||
|
val name = """[a-zA-Z]+""".r
|
||||||
|
|
||||||
|
val moduleTyp: Parser[(ModuleTyp, String)] =
|
||||||
|
("broadcaster" ^^^ (ModuleTyp.Broadcast, "broadcaster")) | ('%' ~ name ^^ { case _ ~ name =>
|
||||||
|
(ModuleTyp.Flip, name)
|
||||||
|
}) |
|
||||||
|
('&' ~ name ^^ { case _ ~ name => (ModuleTyp.Conjunc, name) })
|
||||||
|
val node: Parser[Node] = moduleTyp ~ "->" ~ repsep(name, ",") ^^ { case (typ, name) ~ _ ~ targets =>
|
||||||
|
(typ, name, targets.toSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodes =
|
||||||
|
val ns = lines.map(Parser.parse(Parser.node, _).get).toSeq
|
||||||
|
val edges = ns.flatMap((_, from, to) => to.map((from, _)))
|
||||||
|
def sourcesOf(target: String) = edges.filter(_._2 == target).map(_._1).toSet
|
||||||
|
|
||||||
|
ns.map: (typ, name, targets) =>
|
||||||
|
val mod = typ match
|
||||||
|
case ModuleTyp.Broadcast => Broadcast(targets)
|
||||||
|
case ModuleTyp.Flip => FlipFlop(targets)
|
||||||
|
case ModuleTyp.Conjunc => Conjunc(sourcesOf(name), targets)
|
||||||
|
(name, mod)
|
||||||
|
.toMap
|
||||||
|
|
||||||
|
extension (m: Map[String, Module])
|
||||||
|
def sendSig() = m.loop(mutable.Queue(("", "broadcaster", Low)), Seq.empty)
|
||||||
|
def send(cache: mutable.Map[Map[String, Module], (Map[String, Module], Int, Int)]): (Map[String, Module], Int, Int) =
|
||||||
|
cache.getOrElseUpdate(
|
||||||
|
m, {
|
||||||
|
val (nm, pulses) = m.sendSig()
|
||||||
|
val (lows, highs) = pulses.partition((_, _, p) => p == Low)
|
||||||
|
(nm, lows.size, highs.size)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def loopUntilTwice(
|
||||||
|
breakCond: (String, String, Pulse) => Boolean,
|
||||||
|
first: Seq[Int] = Seq.empty,
|
||||||
|
current: Int = 0
|
||||||
|
): (Int, Int) =
|
||||||
|
println(s"at $current: ${m.hashCode()}")
|
||||||
|
first match
|
||||||
|
case Seq(a, b) => (a, b)
|
||||||
|
case _ =>
|
||||||
|
val (nm, pulses) = sendSig()
|
||||||
|
val nfirst =
|
||||||
|
if pulses.exists(breakCond(_, _, _)) then first :+ current
|
||||||
|
else first
|
||||||
|
nm.loopUntilTwice(breakCond, nfirst, current + 1)
|
||||||
|
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
private def loop(
|
||||||
|
queue: mutable.Queue[(String, String, Pulse)],
|
||||||
|
pulses: Seq[(String, String, Pulse)]
|
||||||
|
): (Map[String, Module], Seq[(String, String, Pulse)]) =
|
||||||
|
if queue.isEmpty then (m, pulses)
|
||||||
|
else
|
||||||
|
val (from, to, pulse) = queue.dequeue()
|
||||||
|
val nm = m.get(to) match
|
||||||
|
case None => m
|
||||||
|
case Some(value) =>
|
||||||
|
val (nmod, nexts) = m(to).handle(from, pulse)
|
||||||
|
if to == "rs" && pulse == High then println(s"$from -> $to pulse = $pulse | $nmod")
|
||||||
|
queue ++= nexts.toIterator.map((target, pulse) => (to, target, pulse))
|
||||||
|
m.updated(to, nmod)
|
||||||
|
nm.loop(queue, (from, to, pulse) +: pulses)
|
||||||
|
|
||||||
|
// part 1
|
||||||
|
def part1 =
|
||||||
|
val mp = mutable.Map.empty[Map[String, Module], (Map[String, Module], Int, Int)]
|
||||||
|
val (_, lows, highs) = (1 to 1000).foldLeft((nodes, 0, 0)):
|
||||||
|
case ((nodes, lows, highs), _) =>
|
||||||
|
val (pn, pl, ph) = nodes.send(mp)
|
||||||
|
(pn, lows + pl, highs + ph)
|
||||||
|
println(1L * lows * highs)
|
||||||
|
|
||||||
|
def part2 =
|
||||||
|
def sendsTo(target: String) = nodes.filter: (name, node) =>
|
||||||
|
node match
|
||||||
|
case Broadcast(targets) => targets.contains(target)
|
||||||
|
case FlipFlop(_, targets) => targets.contains(target)
|
||||||
|
case Conjunc(_, targets) => targets.contains(target)
|
||||||
|
val v = sendsTo("rx").toSeq match // should be at most one
|
||||||
|
case Seq((n, conj)) =>
|
||||||
|
assert(conj.isInstanceOf[Conjunc])
|
||||||
|
n
|
||||||
|
case ss => throw Exception(s"Unexpected $ss")
|
||||||
|
val toV = sendsTo(v)
|
||||||
|
println(toV)
|
||||||
|
// find cycles to send highs to v
|
||||||
|
val res = toV.keys
|
||||||
|
.map(toV => nodes.loopUntilTwice((from, to, pulse) => from == toV && to == v && pulse == High))
|
||||||
|
.foldLeft(1L) { case (n, (a, b)) => lcm(n, b - a) }
|
||||||
|
println(res)
|
||||||
|
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def gcd(a: Long, b: Long): Long = if b == 0 then a else gcd(b, a % b)
|
||||||
|
def lcm(a: Long, b: Long) = a / gcd(a, b) * b
|
||||||
|
|
||||||
|
@main def Day20(part: Int) = part match
|
||||||
|
case 1 => part1
|
||||||
|
case 2 => part2
|
58
inputs/day20.input
Normal file
58
inputs/day20.input
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
%qm -> mj, xn
|
||||||
|
&mj -> hz, bt, lr, sq, qh, vq
|
||||||
|
%qc -> qs, vg
|
||||||
|
%ng -> vr
|
||||||
|
%qh -> sq
|
||||||
|
&bt -> rs
|
||||||
|
%hh -> qs, bx
|
||||||
|
%gk -> cs, bb
|
||||||
|
%js -> mj
|
||||||
|
%pc -> mj, mr
|
||||||
|
%mb -> rd, xs
|
||||||
|
%tp -> qs, ks
|
||||||
|
%xq -> tp, qs
|
||||||
|
%bx -> sz
|
||||||
|
%mn -> cs, md
|
||||||
|
%cv -> rd
|
||||||
|
%rh -> rd, sv
|
||||||
|
%md -> cs
|
||||||
|
%pz -> mj, vq
|
||||||
|
%bz -> rd, hk
|
||||||
|
%jz -> vk
|
||||||
|
%sz -> jz
|
||||||
|
%lr -> pz, mj
|
||||||
|
%xs -> cv, rd
|
||||||
|
%kl -> rd, mb
|
||||||
|
%hz -> pc
|
||||||
|
%hk -> rz, rd
|
||||||
|
%vk -> qc
|
||||||
|
%bh -> zm
|
||||||
|
%vq -> qm
|
||||||
|
%ks -> qs, nd
|
||||||
|
&qs -> dl, jz, bx, vk, vg, hh, sz
|
||||||
|
&dl -> rs
|
||||||
|
%lf -> rh, rd
|
||||||
|
&fr -> rs
|
||||||
|
%xn -> mj, qh
|
||||||
|
%hf -> qs, xq
|
||||||
|
%sv -> rd, ng
|
||||||
|
&rs -> rx
|
||||||
|
&rd -> ng, fr, rz, lf, vr
|
||||||
|
%cj -> ss, cs
|
||||||
|
broadcaster -> hh, lr, bp, lf
|
||||||
|
%zs -> cs, mn
|
||||||
|
%vr -> bz
|
||||||
|
%nd -> qs
|
||||||
|
%jb -> cj, cs
|
||||||
|
&rv -> rs
|
||||||
|
%bp -> cs, lx
|
||||||
|
%ss -> zs
|
||||||
|
%lx -> gk
|
||||||
|
&cs -> lx, ss, rv, bh, bp
|
||||||
|
%bb -> bh, cs
|
||||||
|
%mf -> mj, hz
|
||||||
|
%zm -> cs, jb
|
||||||
|
%mr -> mj, js
|
||||||
|
%rz -> kl
|
||||||
|
%vg -> hf
|
||||||
|
%sq -> mf
|
Loading…
Reference in a new issue