From 4047fc52ac35ff9db9cc36e8262bc976780eb43a Mon Sep 17 00:00:00 2001 From: Soren Date: Thu, 5 Dec 2019 22:11:56 -0800 Subject: [PATCH] Day 6 --- aoc/src/main/scala/aoc/Main.scala | 6 +- aoc/src/main/scala/aoc/y2019/Day06.scala | 74 ++++++++++++++++++++++ aoc/src/main/scala/aoc/y2019/package.scala | 1 + build.sbt | 1 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 aoc/src/main/scala/aoc/y2019/Day06.scala diff --git a/aoc/src/main/scala/aoc/Main.scala b/aoc/src/main/scala/aoc/Main.scala index f8bb629..b644305 100644 --- a/aoc/src/main/scala/aoc/Main.scala +++ b/aoc/src/main/scala/aoc/Main.scala @@ -17,7 +17,11 @@ object Main { val part = askForPart(day) println("Problem input? End input with an `End of Input` line.") val input = LazyList.continually(StdIn.readLine()).takeWhile(_ != "End of Input").toList.mkString("\n") - println(s"Output: ${part(input)}") + val startTime = System.currentTimeMillis() + val output = part(input) + val endTime = System.currentTimeMillis() + println(s"Output: $output") + println(s"Time: ${endTime - startTime}ms") } def askForYear(): Year = { diff --git a/aoc/src/main/scala/aoc/y2019/Day06.scala b/aoc/src/main/scala/aoc/y2019/Day06.scala new file mode 100644 index 0000000..4460814 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/Day06.scala @@ -0,0 +1,74 @@ +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 + } + +} diff --git a/aoc/src/main/scala/aoc/y2019/package.scala b/aoc/src/main/scala/aoc/y2019/package.scala index d09ff74..a525f72 100644 --- a/aoc/src/main/scala/aoc/y2019/package.scala +++ b/aoc/src/main/scala/aoc/y2019/package.scala @@ -8,6 +8,7 @@ package object y2019 extends Year { "3" -> Day03, "4" -> Day04, "5" -> Day05, + "6" -> Day06, ) } diff --git a/build.sbt b/build.sbt index 3cd54ca..fba6031 100644 --- a/build.sbt +++ b/build.sbt @@ -9,6 +9,7 @@ val sharedSettings = Seq( "com.lihaoyi" %%% "fastparse" % "2.1.3", "org.scala-graph" %%% "graph-core" % "1.13.1", "org.typelevel" %%% "cats-core" % "2.0.0", + "org.typelevel" %%% "cats-free" % "2.0.0", ), mainClass := Some("tf.bug.aoc.Main"), )