Skip to content

Commit

Permalink
Day 10: cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
horothesun committed Aug 24, 2024
1 parent 8421dbd commit f47b8a8
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 66 deletions.
105 changes: 55 additions & 50 deletions src/main/scala/Day10.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ object Day10:
case North, South, West, East

enum PipeType:
case Vertical, Horizontal, NorthAndWest, NorthAndEast, SouthAndWest, SouthAndEast
case NorthAndSouth, WestAndEast, NorthAndWest, NorthAndEast, SouthAndWest, SouthAndEast

object PipeType:
def parse(c: Char): Option[PipeType] = c match
case '|' => Some(Vertical)
case '-' => Some(Horizontal)
case '|' => Some(NorthAndSouth)
case '-' => Some(WestAndEast)
case 'J' => Some(NorthAndWest)
case 'L' => Some(NorthAndEast)
case '7' => Some(SouthAndWest)
Expand All @@ -29,12 +29,12 @@ object Day10:
case class CandidateConnections(north: Option[Pos], south: Option[Pos], west: Option[Pos], east: Option[Pos])
object CandidateConnections:
def parse(pipeType: PipeType, pos: Pos): CandidateConnections = pipeType match
case Vertical => CandidateConnections(Some(pos.north), Some(pos.south), west = None, east = None)
case Horizontal => CandidateConnections(north = None, south = None, Some(pos.west), Some(pos.east))
case NorthAndWest => CandidateConnections(Some(pos.north), south = None, Some(pos.west), east = None)
case NorthAndEast => CandidateConnections(Some(pos.north), south = None, west = None, Some(pos.east))
case SouthAndWest => CandidateConnections(north = None, Some(pos.south), Some(pos.west), east = None)
case SouthAndEast => CandidateConnections(north = None, Some(pos.south), west = None, Some(pos.east))
case NorthAndSouth => CandidateConnections(Some(pos.north), Some(pos.south), west = None, east = None)
case WestAndEast => CandidateConnections(north = None, south = None, Some(pos.west), Some(pos.east))
case NorthAndWest => CandidateConnections(Some(pos.north), south = None, Some(pos.west), east = None)
case NorthAndEast => CandidateConnections(Some(pos.north), south = None, west = None, Some(pos.east))
case SouthAndWest => CandidateConnections(north = None, Some(pos.south), Some(pos.west), east = None)
case SouthAndEast => CandidateConnections(north = None, Some(pos.south), west = None, Some(pos.east))

case class Pos(row: Int, col: Int):
lazy val north: Pos = Pos(row - 1, col)
Expand All @@ -43,10 +43,10 @@ object Day10:
lazy val east: Pos = Pos(row, col + 1)

def cardinalDirectionOf(that: Pos): Option[CardinalDirection] =
if (that == north) Some(North)
else if (that == south) Some(South)
else if (that == west) Some(West)
else if (that == east) Some(East)
if (north == that) Some(North)
else if (south == that) Some(South)
else if (west == that) Some(West)
else if (east == that) Some(East)
else None

object Pos:
Expand All @@ -70,16 +70,18 @@ object Day10:

lazy val startAs: Option[PipeType] =
def nextCardinalDirection(path: NonEmptyList[Pos]): Option[CardinalDirection] =
val s = firstPath.head
path.tail.headOption.flatMap(s.cardinalDirectionOf)
path.tail.headOption.flatMap(firstPath.head.cardinalDirectionOf)

(nextCardinalDirection(firstPath), nextCardinalDirection(secondPath)).flatMapN {
case (North, South) | (South, North) => Some(Vertical)
(
nextCardinalDirection(firstPath),
nextCardinalDirection(secondPath)
).flatMapN {
case (North, South) | (South, North) => Some(NorthAndSouth)
case (North, West) | (West, North) => Some(NorthAndWest)
case (North, East) | (East, North) => Some(NorthAndEast)
case (South, West) | (West, South) => Some(SouthAndWest)
case (South, East) | (East, South) => Some(SouthAndEast)
case (West, East) | (East, West) => Some(Horizontal)
case (West, East) | (East, West) => Some(WestAndEast)
case _ => None
}

Expand All @@ -90,7 +92,7 @@ object Day10:
case InsideLoop, OutsideLoop, OnLoop

enum Inversion:
case Straight, NorthToSouth, SouthToNorth
case Vertical, NorthToSouth, SouthToNorth

case class Field(rows: Vector[Vector[Tile]]):
def startPos: Option[Pos] =
Expand Down Expand Up @@ -119,22 +121,22 @@ object Day10:
).mapFilter(identity).toSet

def oneStepNorthOfStart(pos: Pos): Option[Pos] = get(pos.north).collect {
case t @ Pipe(Vertical) => t
case t @ Pipe(SouthAndWest) => t
case t @ Pipe(SouthAndEast) => t
case t @ Pipe(NorthAndSouth) => t
case t @ Pipe(SouthAndWest) => t
case t @ Pipe(SouthAndEast) => t
}.map(_ => pos.north)
def oneStepSouthOfStart(pos: Pos): Option[Pos] = get(pos.south).collect {
case t @ Pipe(Vertical) => t
case t @ Pipe(NorthAndWest) => t
case t @ Pipe(NorthAndEast) => t
case t @ Pipe(NorthAndSouth) => t
case t @ Pipe(NorthAndWest) => t
case t @ Pipe(NorthAndEast) => t
}.map(_ => pos.south)
def oneStepWestOfStart(pos: Pos): Option[Pos] = get(pos.west).collect {
case t @ Pipe(Horizontal) => t
case t @ Pipe(WestAndEast) => t
case t @ Pipe(NorthAndEast) => t
case t @ Pipe(SouthAndEast) => t
}.map(_ => pos.west)
def oneStepEastOfStart(pos: Pos): Option[Pos] = get(pos.east).collect {
case t @ Pipe(Horizontal) => t
case t @ Pipe(WestAndEast) => t
case t @ Pipe(NorthAndWest) => t
case t @ Pipe(SouthAndWest) => t
}.map(_ => pos.east)
Expand All @@ -144,12 +146,12 @@ object Day10:
optPs.toList.mapFilter(optP => optP.flatMap(p => get(p).map(_ => p))).toSet
val cc = CandidateConnections.parse(pipeType, pos)
pipeType match
case Vertical => validTiles(cc.north, cc.south)
case Horizontal => validTiles(cc.west, cc.east)
case NorthAndWest => validTiles(cc.north, cc.west)
case NorthAndEast => validTiles(cc.north, cc.east)
case SouthAndWest => validTiles(cc.south, cc.west)
case SouthAndEast => validTiles(cc.south, cc.east)
case NorthAndSouth => validTiles(cc.north, cc.south)
case WestAndEast => validTiles(cc.west, cc.east)
case NorthAndWest => validTiles(cc.north, cc.west)
case NorthAndEast => validTiles(cc.north, cc.east)
case SouthAndWest => validTiles(cc.south, cc.west)
case SouthAndEast => validTiles(cc.south, cc.east)

lazy val loop: Option[Loop] = for {
s <- startPos
Expand Down Expand Up @@ -192,7 +194,8 @@ object Day10:
.map(r => Range(start = 0, end = rows(r).length).map(c => Pos(r, c)).toVector)

def tileTypeAt(l: Loop, startAs: PipeType, pos: Pos): TileType =
if (l.allPositions.contains(pos)) TileType.OnLoop
import TileType.*
if (l.allPositions.contains(pos)) OnLoop
else
val row = rows(pos.row).zipWithIndex.map { (tile, col) =>
import TileRawType.*
Expand All @@ -201,7 +204,7 @@ object Day10:
}
val inversionsCount = inversionsToEast(fromCol = pos.col, startAs, row).length
val isEven: Int => Boolean = _ % 2 == 0
if (isEven(inversionsCount)) TileType.OutsideLoop else TileType.InsideLoop
if (isEven(inversionsCount)) OutsideLoop else InsideLoop

object Field:
def parse(input: List[String]): Option[Field] =
Expand All @@ -211,37 +214,39 @@ object Day10:
row
.drop(fromCol)
.mapFilter { (tile, tileRawType) =>
import TileRawType.*
tileRawType match
case TileRawType.OnLoop => Some(tile)
case TileRawType.NotOnLoop => None
case OnLoop => Some(tile)
case NotOnLoop => None
}
.mapFilter {
case Ground | Pipe(Horizontal) => None
case Start => Some(startAs)
case Pipe(pipeType) => Some(pipeType)
case Ground | Pipe(WestAndEast) => None
case Start => Some(startAs)
case Pipe(pipeType) => Some(pipeType)
}
.foldLeft[(List[Inversion], Option[PipeType])]((List.empty, None)) { case ((acc, open), pipeType) =>
pipeType match
case Vertical /* | */ => (acc :+ Straight, None)
case Horizontal /* - */ => (acc, open)
case NorthAndEast /* L */ | SouthAndEast /* F */ => (acc, Some(pipeType))
case NorthAndWest /* J */ =>
case NorthAndSouth => (acc :+ Vertical, None)
case WestAndEast => (acc, open)
case NorthAndEast | SouthAndEast => (acc, Some(pipeType))
case NorthAndWest =>
open match
case Some(SouthAndEast) /* F */ => (acc :+ SouthToNorth, None)
case _ => (acc, None)
case SouthAndWest /* 7 */ =>
case Some(SouthAndEast) => (acc :+ SouthToNorth, None)
case _ => (acc, None)
case SouthAndWest =>
open match
case Some(NorthAndEast) /* L */ => (acc :+ NorthToSouth, None)
case _ => (acc, None)
case Some(NorthAndEast) => (acc :+ NorthToSouth, None)
case _ => (acc, None)
}
._1

def stepsCountToFarthestInLoop(input: List[String]): Option[Int] =
Field.parse(input).flatMap(_.loop.map(_.firstPath.length - 1))

def countTilesInsideLoop(input: List[String]): Option[Int] =
import TileType.*
for {
field <- Field.parse(input)
loop <- field.loop
startAs <- loop.startAs
} yield field.allTileTypes(loop, startAs).map(_.count(_ == TileType.InsideLoop)).sum
} yield field.allTileTypes(loop, startAs).map(_.count(_ == InsideLoop)).sum
33 changes: 17 additions & 16 deletions src/test/scala/Day10Suite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ import Day10.CardinalDirection.*
import Day10.Inversion.*
import Day10.PipeType.*
import Day10.Tile.*
import Day10.TileRawType.*
import Day10Suite.*

class Day10Suite extends ScalaCheckSuite:

test("parsing smallInput1"):
val expected = Field(rows =
Vector(
Vector(Horizontal, NorthAndEast, Vertical, SouthAndEast, SouthAndWest).map(Pipe.apply),
Vector(Pipe(SouthAndWest), Start, Pipe(Horizontal), Pipe(SouthAndWest), Pipe(Vertical)),
Vector(NorthAndEast, Vertical, SouthAndWest, Vertical, Vertical).map(Pipe.apply),
Vector(Horizontal, NorthAndEast, Horizontal, NorthAndWest, Vertical).map(Pipe.apply),
Vector(NorthAndEast, Vertical, Horizontal, NorthAndWest, SouthAndEast).map(Pipe.apply)
Vector(WestAndEast, NorthAndEast, NorthAndSouth, SouthAndEast, SouthAndWest).map(Pipe.apply),
Vector(Pipe(SouthAndWest), Start, Pipe(WestAndEast), Pipe(SouthAndWest), Pipe(NorthAndSouth)),
Vector(NorthAndEast, NorthAndSouth, SouthAndWest, NorthAndSouth, NorthAndSouth).map(Pipe.apply),
Vector(WestAndEast, NorthAndEast, WestAndEast, NorthAndWest, NorthAndSouth).map(Pipe.apply),
Vector(NorthAndEast, NorthAndSouth, WestAndEast, NorthAndWest, SouthAndEast).map(Pipe.apply)
)
)
assertEquals(Field.parse(smallInput1), Some(expected))
Expand All @@ -28,9 +27,9 @@ class Day10Suite extends ScalaCheckSuite:
val expected = Field(rows =
Vector(
Vector.fill(5)(Ground),
Vector(Ground, Start, Pipe(Horizontal), Pipe(SouthAndWest), Ground),
Vector(Ground, Pipe(Vertical), Ground, Pipe(Vertical), Ground),
Vector(Ground, Pipe(NorthAndEast), Pipe(Horizontal), Pipe(NorthAndWest), Ground),
Vector(Ground, Start, Pipe(WestAndEast), Pipe(SouthAndWest), Ground),
Vector(Ground, Pipe(NorthAndSouth), Ground, Pipe(NorthAndSouth), Ground),
Vector(Ground, Pipe(NorthAndEast), Pipe(WestAndEast), Pipe(NorthAndWest), Ground),
Vector.fill(5)(Ground)
)
)
Expand Down Expand Up @@ -183,6 +182,7 @@ class Day10Suite extends ScalaCheckSuite:
forAll(posGen)(p => assertEquals(p.cardinalDirectionOf(p.north.east), None))

property("inversions to East are empty when loop is NOT in the row, from any column and for any row"):
import TileRawType.*
val n = 10
forAll(
Gen.zip(
Expand All @@ -195,32 +195,33 @@ class Day10Suite extends ScalaCheckSuite:
}

test(
"inversions to East are [Straight, NorthToSouth, Straight, SouthToNorth]" +
"inversions to East are [Vertical, NorthToSouth, Vertical, SouthToNorth]" +
" for row with loop \"..|..LS7|.F--J.\", 'S' as '-', from leftmost column"
):
import TileRawType.*
assertEquals(
Field.inversionsToEast(
fromCol = 0,
startAs = Horizontal,
startAs = WestAndEast,
row = Vector(
(Ground, NotOnLoop),
(Ground, NotOnLoop),
(Pipe(Vertical), OnLoop),
(Pipe(NorthAndSouth), OnLoop),
(Ground, NotOnLoop),
(Ground, NotOnLoop),
(Pipe(NorthAndEast), OnLoop),
(Start, OnLoop),
(Pipe(SouthAndWest), OnLoop),
(Pipe(Vertical), OnLoop),
(Pipe(NorthAndSouth), OnLoop),
(Ground, NotOnLoop),
(Pipe(SouthAndEast), OnLoop),
(Pipe(Horizontal), OnLoop),
(Pipe(Horizontal), OnLoop),
(Pipe(WestAndEast), OnLoop),
(Pipe(WestAndEast), OnLoop),
(Pipe(NorthAndWest), OnLoop),
(Ground, NotOnLoop)
)
),
List(Straight, NorthToSouth, Straight, SouthToNorth)
List(Vertical, NorthToSouth, Vertical, SouthToNorth)
)

test("Field from smallInput3 has 4 tiles inside the loop"):
Expand Down

0 comments on commit f47b8a8

Please sign in to comment.