diff --git a/aoc/src/main/scala/aoc/y2019/Day03.scala b/aoc/src/main/scala/aoc/y2019/Day03.scala new file mode 100644 index 0000000..986e848 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/Day03.scala @@ -0,0 +1,102 @@ +package aoc.y2019 + +import java.util.UUID + +import aoc.Day +import cats._ +import cats.implicits._ +import cats.data.State + +object Day03 extends Day { + + sealed trait Direction + case object Up extends Direction + case object Down extends Direction + case object Left extends Direction + case object Right extends Direction + case class Move(dir: Direction, spaces: Int) { + + val directions = Vector.fill(spaces)(dir) + + } + + case class Wire(moves: Vector[Move]) + + import fastparse._, SingleLineWhitespace._ + + def parseUp[_: P]: P[Up.type] = P("U").map(_ => Up) + def parseDown[_: P]: P[Down.type] = P("D").map(_ => Down) + def parseLeft[_: P]: P[Left.type] = P("L").map(_ => Left) + def parseRight[_: P]: P[Right.type] = P("R").map(_ => Right) + def parseDirection[_: P]: P[Direction] = parseUp | parseDown | parseLeft | parseRight + + def parseNumber[_: P]: P[Int] = P(CharIn("0-9").rep(1).!).map(_.toInt) + + def parseMove[_: P]: P[Move] = P(parseDirection ~ parseNumber).map { + case (direction, spaces) => Move(direction, spaces) + } + + def parseLine[_: P]: P[Vector[Move]] = P(parseMove.rep(1, ",")).map(_.toVector) + + case class Coordinate(x: Int, y: Int) { + + def move(direction: Direction, times: Int = 1): Coordinate = direction match { + case Up => Coordinate(x, y + times) + case Down => Coordinate(x, y - times) + case Left => Coordinate(x - times, y) + case Right => Coordinate(x + times, y) + } + + def manhattanDistance: Int = Math.abs(x) + Math.abs(y) + + } + + val emptyBoard: Board = (Vector(), Vector()) + type Board = (Vector[Coordinate], Vector[Coordinate]) + type BoardModification = State[Board, Unit] + + def lay(wires: (Wire, Wire)): BoardModification = { + wires match { + case (Wire(leftWireMoves), Wire(rightWireMoves)) => + val leftWireDirections = leftWireMoves.flatMap(_.directions) + val rightWireDirections = rightWireMoves.flatMap(_.directions) + val leftWireOptions = leftWireDirections.map(Some(_)).padTo(rightWireDirections.length, None) + val rightWireOptions = rightWireDirections.map(Some(_)).padTo(leftWireDirections.length, None) + val moves = leftWireOptions.zip(rightWireOptions) + moves.traverse(move).map(_ => ()) + } + } + + def move(move: (Option[Direction], Option[Direction])): BoardModification = State.modify { + case (leftWire, rightWire) => move match { + case (leftMove, rightMove) => + ( + leftMove.fold(leftWire)(d => leftWire.appended(leftWire.lastOption.getOrElse(Coordinate(0, 0)).move(d))), + rightMove.fold(rightWire)(d => rightWire.appended(rightWire.lastOption.getOrElse(Coordinate(0, 0)).move(d))) + ) + } + } + + def endingBoard(input: String): (Vector[Coordinate], Vector[Coordinate]) = { + val Vector(wireOneStr, wireTwoStr) = input.linesIterator.toVector + val wireOne = Wire(parse(wireOneStr, parseLine(_)).get.value) + val wireTwo = Wire(parse(wireTwoStr, parseLine(_)).get.value) + val layWires = lay(wireOne, wireTwo) + val endingBoard: Board = layWires.runS(emptyBoard).value + endingBoard + } + + override def part1(input: String): String = { + val (leftCoords, rightCoords) = endingBoard(input) + val intersections = leftCoords.intersect(rightCoords) + intersections.map(_.manhattanDistance).min.toString + } + + override def part2(input: String): String = { + val (leftCoords, rightCoords) = endingBoard(input) + val intersections = leftCoords.intersect(rightCoords) + val intersectionSteps = intersections.map(c => leftCoords.indexOf(c) + rightCoords.indexOf(c) + 2) + intersectionSteps.min.toString + } + +} diff --git a/aoc/src/main/scala/aoc/y2019/package.scala b/aoc/src/main/scala/aoc/y2019/package.scala index f61d5c7..1bf5377 100644 --- a/aoc/src/main/scala/aoc/y2019/package.scala +++ b/aoc/src/main/scala/aoc/y2019/package.scala @@ -5,6 +5,7 @@ package object y2019 extends Year { override def days: Map[String, Day] = Map( "1" -> Day01, "2" -> Day02, + "3" -> Day03, ) }