aoc2023/Day19.scala

139 lines
3.6 KiB
Scala
Raw Normal View History

2023-12-19 16:11:26 +00:00
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