139 lines
3.6 KiB
Scala
139 lines
3.6 KiB
Scala
|
package aoc.day19
|
||
|
|
||
|
import aoc._
|
||
|
|
||
|
enum Rate:
|
||
|
case X, M, A, S
|
||
|
import Rate._
|
||
|
|
||
|
case class StateT[T](s: Array[T]):
|
||
|
assert(s.size == 4)
|
||
|
def apply(r: Rate) = s(r.ordinal)
|
||
|
inline def update(r: Rate)(inline upd: T => T) =
|
||
|
val t = s.clone()
|
||
|
t(r.ordinal) = upd(this(r))
|
||
|
StateT(t)
|
||
|
|
||
|
type State = StateT[Int]
|
||
|
|
||
|
// [low, high)
|
||
|
case class Range(low: Int, high: Int):
|
||
|
def isEmpty = low >= high
|
||
|
inline def size = (high - low)
|
||
|
inline def withMin(min: Int) = Range(low.max(min), high)
|
||
|
inline def withMax(max: Int) = Range(low, high.min(max))
|
||
|
object Range:
|
||
|
lazy val empty = Range(1, 4001)
|
||
|
|
||
|
type Ranges = StateT[Range]
|
||
|
|
||
|
extension (rs: Ranges)
|
||
|
def isEmpty = rs.s.exists(_.isEmpty)
|
||
|
|
||
|
def size = rs.s.map(_.size.toLong).product
|
||
|
|
||
|
def passing(r: Rule) = r match
|
||
|
case Rule.Lt(r, v, _) =>
|
||
|
rs.update(r)(_.withMax(v))
|
||
|
case Rule.Gt(r, v, _) =>
|
||
|
rs.update(r)(_.withMin(v + 1))
|
||
|
|
||
|
def failing(r: Rule) = r match
|
||
|
case Rule.Lt(r, v, _) =>
|
||
|
rs.update(r)(_.withMin(v))
|
||
|
case Rule.Gt(r, v, _) =>
|
||
|
rs.update(r)(_.withMax(v + 1))
|
||
|
|
||
|
enum Rule:
|
||
|
case Lt(r: Rate, v: Int, andThenTarget: String)
|
||
|
case Gt(r: Rate, v: Int, andThenTarget: String)
|
||
|
|
||
|
def test(s: State) = this match
|
||
|
case Lt(r, v, _) => s(r) < v
|
||
|
case Gt(r, v, _) => s(r) > v
|
||
|
|
||
|
inline def andThen = this match
|
||
|
case Lt(_, _, andThen) => andThen
|
||
|
case Gt(_, _, andThen) => andThen
|
||
|
|
||
|
case class Work(rules: Seq[Rule], andThen: String):
|
||
|
def apply(s: State) =
|
||
|
rules
|
||
|
.find(_.test(s))
|
||
|
.map(_.andThen)
|
||
|
.getOrElse(andThen)
|
||
|
|
||
|
extension (m: Map[String, Work])
|
||
|
@scala.annotation.tailrec
|
||
|
def test(cur: String)(s: State): Boolean =
|
||
|
cur match
|
||
|
case "A" => true
|
||
|
case "R" => false
|
||
|
case _ => test(m(cur)(s))(s)
|
||
|
|
||
|
object Parser extends CommonParser:
|
||
|
val name = """[a-zA-Z]+""".r
|
||
|
val rate =
|
||
|
'x' ^^^ X | 'm' ^^^ M | 'a' ^^^ A | 's' ^^^ S
|
||
|
|
||
|
val rule =
|
||
|
val constr = '<' ^^^ (Rule.Lt(_, _, _)) | '>' ^^^ (Rule.Gt(_, _, _))
|
||
|
rate ~ constr ~ num ~ ":" ~ name ^^ { case rate ~ constr ~ num ~ _ ~ target =>
|
||
|
constr(rate, num, target)
|
||
|
}
|
||
|
val work =
|
||
|
val rules = rep(rule <~ ",")
|
||
|
name ~ "{" ~ rules ~ name ~ "}" ^^ { case name ~ _ ~ rules ~ andThen ~ _ =>
|
||
|
(name -> Work(rules, andThen))
|
||
|
}
|
||
|
|
||
|
val state =
|
||
|
val stat = rate ~ "=" ~ num ^^ { case rate ~ _ ~ num => (rate, num) }
|
||
|
"{" ~ repsep(stat, ",") ~ "}" ^^ { case _ ~ stats ~ _ =>
|
||
|
StateT(stats.foldLeft(Array(0, 0, 0, 0)) { case (arr, (rate, num)) =>
|
||
|
arr(rate.ordinal) = num; arr
|
||
|
})
|
||
|
}
|
||
|
|
||
|
val (works, states) =
|
||
|
lines.toSeq.splitBy(_ == "") match
|
||
|
case Seq(works, states) =>
|
||
|
(works.map(Parser.parse(Parser.work, _).get).toMap, states.map(Parser.parse(Parser.state, _).get))
|
||
|
case _ => throw Exception("Expected two groupings")
|
||
|
|
||
|
// part 1
|
||
|
|
||
|
def part1 = println(
|
||
|
states
|
||
|
.filter(works.test("in"))
|
||
|
.map(_.s.sum)
|
||
|
.sum
|
||
|
)
|
||
|
|
||
|
// part 2
|
||
|
|
||
|
extension (m: Map[String, Work])
|
||
|
def combinations(cur: String)(visited: Set[String], ranges: Ranges): Long =
|
||
|
cur match
|
||
|
case "A" => ranges.size
|
||
|
case "R" => 0
|
||
|
case _ if ranges.isEmpty => 0
|
||
|
case _ =>
|
||
|
val work = m(cur)
|
||
|
var curRanges = ranges
|
||
|
val passings = work.rules.map: rule =>
|
||
|
val passing = curRanges.passing(rule)
|
||
|
curRanges = curRanges.failing(rule)
|
||
|
(passing, rule.andThen)
|
||
|
((curRanges, work.andThen) +: passings)
|
||
|
.filter((_, t) => !visited.contains(t))
|
||
|
.map((r, t) => combinations(t)(visited + t, r))
|
||
|
.sum
|
||
|
|
||
|
def part2 =
|
||
|
println(works.combinations("in")(Set.empty, StateT(Array.fill(4)(Range.empty))))
|
||
|
|
||
|
@main def Day19(part: Int) = part match
|
||
|
case 1 => part1
|
||
|
case 2 => part2
|