diff --git a/aoc/src/main/scala/aoc/y2018/Day04.scala b/aoc/src/main/scala/aoc/y2018/Day04.scala new file mode 100644 index 0000000..f8ec09a --- /dev/null +++ b/aoc/src/main/scala/aoc/y2018/Day04.scala @@ -0,0 +1,168 @@ +package aoc.y2018 +import aoc.Day + +// Todo: Seriously. Rewrite this. This is garbage. +object Day04 extends Day { + + import fastparse._, NoWhitespace._ + + def padNum(n: Int, l: Int): String = { + n.toString.reverse.padTo(l, '0').reverse + } + + case class Date(year: Int, month: Int, day: Int) { + def di: Int = year * 10000 + month * 100 + day + } + + case class Time(hour: Int, minute: Int) { + def mi: Int = hour * 60 + minute + } + + case class DateTime(d: Date, t: Time) { + override def toString: String = + s"${padNum(d.year, 4)}-${padNum(d.month, 2)}-${padNum(d.day, 2)} ${padNum(t.hour, 2)}:${padNum(t.minute, 2)}" + } + + sealed trait Event { + def wake: Boolean + } + case class Shift(g: Int) extends Event { + override def wake: Boolean = true + } + case object Sleep extends Event { + override def wake: Boolean = false + } + case object Wake extends Event { + override def wake: Boolean = true + } + + case class Action(t: DateTime, e: Event) + + def digits[_: P]: P[Int] = P(CharIn("0-9").rep(1).!).map(_.toInt) + + def dateParser[_: P]: P[Date] = P(digits ~ "-" ~ digits ~ "-" ~ digits).map { + case (y, m, d) => Date(y, m, d) + } + + def timeParser[_: P]: P[Time] = P(digits ~ ":" ~ digits).map { + case (h, m) => Time(h, m) + } + + def dateTimeParser[_: P]: P[DateTime] = P("[" ~ dateParser ~ " " ~/ timeParser ~ "]").map { + case (d, t) => shiftByHours(DateTime(d, t), 12) + } + def shiftParser[_: P]: P[Shift] = P("Guard #" ~ digits ~ " begins shift").map(Shift) + def sleepParser[_: P]: P[Sleep.type] = P("falls asleep").map(_ => Sleep) + def wakeParser[_: P]: P[Wake.type] = P("wakes up").map(_ => Wake) + def eventParser[_: P]: P[Event] = P(shiftParser | sleepParser | wakeParser) + + def actionParser[_: P]: P[Action] = P(dateTimeParser ~ " " ~ eventParser).map { + case (d, e) => Action(d, e) + } + def actionsParser[_: P]: P[List[Action]] = P(actionParser.rep(1, "\n"./)).map(_.toList) + + def getActions(input: String): List[Action] = { + val Parsed.Success(l, _) = parse(input, actionsParser(_)) + l.sortBy(_.t.toString) + } + + def shiftByHours(d: DateTime, n: Int): DateTime = { + val h = d.t.hour + val (da, nh) = ((h + n) / 24, (h + n) % 24) + d.copy(d = shiftByDays(d, da).d, t = d.t.copy(hour = nh)) + } + + val monthDayMap = Map( + 1 -> 31, + 2 -> 28, + 3 -> 31, + 4 -> 30, + 5 -> 31, + 6 -> 30, + 7 -> 31, + 8 -> 31, + 9 -> 30, + 10 -> 31, + 11 -> 30, + 12 -> 31 + ) + + def shiftByDays(d: DateTime, n: Int): DateTime = { + val m = monthDayMap(d.d.month) + val a = d.d.day + if (n > 1) throw new NotImplementedError("Month modulos not accounted for yet!") + val (ma, nd) = ((a + n) / m, (a + n) % m) + val nm = d.d.month + ma + d.copy(d = d.d.copy(month = nm, day = nd)) + } + + case class Day(d: Date, g: Option[Int], mins: List[Minute]) + case class Minute(state: Boolean) extends AnyVal + case class State(d: List[Day] = List(), qg: List[Int] = List(), cm: Int = 0) { + + def step(oa: Option[Action]): State = { + print(s"\r${d.size}\t\t$cm\t\t") + val (nqg, acdl): (List[Int], Option[Day]) = oa match { + case Some(a) => + d.headOption match { + case Some(cd) => + a.e match { + case Shift(ng) => { + cd.g match { + case Some(_) => (qg :+ ng, Some(cd)) + case None => (qg, Some(cd.copy(g = Some(ng)))) + } + } + case Wake => (qg, Some(cd.copy(mins = cd.mins :+ Minute(true)))) + case Sleep => (qg, Some(cd.copy(mins = cd.mins :+ Minute(false)))) + } + case None => + a.e match { + case Shift(g) => (qg, Some(Day(a.t.d, Some(g), List.fill(a.t.t.mi)(Minute(true))))) + } + } + case None => + d.headOption match { + case Some(cd) => + (qg, Some(cd.copy(mins = cd.mins :+ cd.mins.last))) + case None => + (qg, None) + } + } + val nd = if (cm == 0 || d.isEmpty) { + acdl.fold(d)(_ :: d) + } else { + acdl.fold(d.tail)(_ :: d.tail) + } + val ncm = (cm + 1) % (24 * 60) + State(nd, nqg, ncm) + } + + } + + override def part1(input: String): String = { + return ??? // Fixme: This all sucks and is broken please send an ambulance + val actions = getActions(input) + println(actions.mkString("\n")) + val dayGroups = actions.groupBy(_.t.d) + val fleshedOut: Map[Date, List[Option[Action]]] = dayGroups.mapValues(l => { + val zipped = l.map(_.t.t.mi).zip(l).toMap + List.tabulate(60 * 2)(n => zipped.get(n + (60 * 11))) + }) + val endState = fleshedOut.foldLeft(State()) { + case (s, (_, al)) => + al.foldLeft(s)(_.step(_)) + } + val days = endState.d + println() + val Day(_, Some(mostSleep), _) = days.maxBy(_.mins.count(!_.state)) + println(days.map(_.mins.size).mkString("\n")) + val maxMinutes = days.map(_.mins.size).max + val minutesToDays = days.map(_.mins.padTo(maxMinutes, Minute(true))).transpose + val (_, mostConsistency) = minutesToDays.zipWithIndex.maxBy { case (l, _) => l.count(!_.state) } + (mostSleep * mostConsistency).toString + } + + override def part2(input: String): String = ??? + +} diff --git a/aoc/src/main/scala/aoc/y2018/package.scala b/aoc/src/main/scala/aoc/y2018/package.scala index a02dc3d..b9db9b4 100644 --- a/aoc/src/main/scala/aoc/y2018/package.scala +++ b/aoc/src/main/scala/aoc/y2018/package.scala @@ -6,6 +6,7 @@ package object y2018 extends Year { "1" -> Day01, "2" -> Day02, "3" -> Day03, + "4" -> Day04, ) }