aoc-old/aoc/src/main/scala/aoc/y2019/Day06.scala

75 lines
2.6 KiB
Scala

package aoc.y2019
import aoc.Day
import cats._
import cats.implicits._
import cats.data._
import cats.free._
import scala.annotation.tailrec
object Day06 extends Day {
case class Body(name: String)
type Orbits = Cofree[Vector, Body]
import fastparse._, SingleLineWhitespace._
def parseBody[_: P]: P[Body] = P(CharIn("A-Z", "0-9").rep.!).map(Body)
def parseOrbit[_: P]: P[(Body, Body)] = P(parseBody ~ ")" ~ parseBody)
def parseOrbitList[_: P]: P[Vector[(Body, Body)]] = P(parseOrbit.rep(1, "\n")).map(_.toVector)
def constructOrbits(orbitMap: Map[Body, Vector[Body]], initial: Body): Orbits = {
lazy val childOrbits = orbitMap.getOrElse(initial, Vector.empty[Body]).map(constructOrbits(orbitMap, _))
Cofree(initial, Eval.later(childOrbits))
}
def indirectOrbits(orbits: Orbits, level: Int = 0): Int = {
val childOrbits = orbits.tail.value.map(indirectOrbits(_, level + 1))
childOrbits.sum + level
}
def hasBody(orbits: Orbits, body: Body): Boolean = {
orbits.head == body || orbits.tail.value.exists(hasBody(_, body))
}
@tailrec def closestCommonOrbit(orbits: Orbits, a: Body, b: Body): Option[Orbits] = {
def bothChildren(orbits: Orbits): Option[Orbits] = Option.when(hasBody(orbits, a) && hasBody(orbits, b))(orbits)
orbits.tail.value.collectFirstSome(bothChildren) match {
case Some(closerCommonOrbit) => closestCommonOrbit(closerCommonOrbit, a, b)
case None => bothChildren(orbits)
}
}
def distanceToChildBody(orbits: Orbits, target: Body, depth: Int = 0): Option[Int] = {
if(orbits.head == target) depth.some
else orbits.tail.value match {
case Vector() => None
case nonEmpty => nonEmpty.map(distanceToChildBody(_, target, depth + 1)).find(_.isDefined).flatten
}
}
def orbitsFromInput(input: String): Orbits = {
val orbitList = parse(input, parseOrbitList(_)).get.value
val orbitMap = orbitList.groupBy(_._1).view.mapValues(_.map(_._2)).toMap
val firstPlanet = orbitMap.keys.find(body => orbitMap.values.count(_.contains(body)) == 0).get
constructOrbits(orbitMap, firstPlanet)
}
override def part1(input: String): String = {
indirectOrbits(orbitsFromInput(input)).toString
}
override def part2(input: String): String = {
val orbits = orbitsFromInput(input)
val orbitalTransfers = (for {
closestCommon <- closestCommonOrbit(orbits, Body("YOU"), Body("SAN"))
distanceToMe <- distanceToChildBody(closestCommon, Body("YOU"))
distanceToSan <- distanceToChildBody(closestCommon, Body("SAN"))
} yield (distanceToMe + distanceToSan) - 2).get
orbitalTransfers.toString
}
}