Skip to content

Commit e6c1f8f

Browse files
committed
Day 6: part 1
1 parent f675566 commit e6c1f8f

File tree

2 files changed

+103
-34
lines changed

2 files changed

+103
-34
lines changed

src/main/scala/Day06.scala

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Day06.Cell.*
22
import Day06.Direction.*
33
import cats.data.Store
4-
import cats.derived.*
54
import cats.syntax.all.*
65

76
object Day06:
@@ -13,7 +12,7 @@ object Day06:
1312
def right: Pos = Pos(row, col + 1)
1413
def down: Pos = Pos(row + 1, col)
1514

16-
def next(dir: Direction): Pos = dir match {
15+
def next(direction: Direction): Pos = direction match {
1716
case Left => left
1817
case Up => up
1918
case Right => right
@@ -42,19 +41,53 @@ object Day06:
4241
enum Tile:
4342
case Empty, Obstruction
4443

44+
object Tile:
45+
def parse(c: Char): Option[Tile] = c match {
46+
case '.' => Empty.some
47+
case '#' => Obstruction.some
48+
case _ => None
49+
}
50+
4551
enum Cell:
4652
case TileCell(tile: Tile)
4753
case GuardCell(direction: Direction)
4854

49-
type LabWithNoGuardStore = Store[Pos, Option[Tile]]
55+
def toTile: Tile = this match {
56+
case TileCell(tile) => tile
57+
case GuardCell(_) => Tile.Empty
58+
}
59+
60+
object Cell:
61+
def parse(c: Char): Option[Cell] =
62+
Tile.parse(c).map(TileCell.apply).orElse(Direction.parse(c).map(GuardCell.apply))
5063

5164
type LabStore = Store[Pos, Option[(Tile, Guard)]]
5265

53-
def extractGuard(store: LabStore): Option[Guard] = store.extract.map(_._2)
66+
val unguardedLabStore: LabStore = {
67+
val outOfBoundsPos = Pos(row = -1, col = -1)
68+
Store(
69+
{
70+
case Pos(row = 0, col = 0) => (Tile.Empty, Guard(outOfBoundsPos, Direction.Left)).some
71+
case _ => None
72+
},
73+
s = outOfBoundsPos
74+
)
75+
}
76+
77+
extension (store: LabStore)
78+
79+
def getGuard: Option[Guard] = store.extract.map(_._2)
80+
81+
def afterGuardStep: LabStore =
82+
(for {
83+
oldGuard <- store.getGuard
84+
newGuard <- oldGuard.afterStep(store)
85+
newStore = store.seek(newGuard.pos).map(_.map((tile, _) => (tile, newGuard)))
86+
} yield newStore).getOrElse(unguardedLabStore)
5487

5588
case class Guard(pos: Pos, direction: Direction):
5689

57-
def updated(store: LabStore): Option[Guard] =
90+
def afterStep(store: LabStore): Option[Guard] =
5891
val nextPos = pos.next(direction)
5992
store
6093
.peek(nextPos)
@@ -67,36 +100,28 @@ object Day06:
67100

68101
val toVectors: Vector[Vector[Cell]] = cells.map(_.toVector).toVector
69102

70-
val outOfBoundsPos: Pos = Pos(row = -1, col = -1)
71-
72-
val toStoreWithNoGuard: LabWithNoGuardStore =
103+
val toStore: LabStore =
73104
Store(
74-
p =>
75-
toVectors
76-
.get(p.row)
77-
.flatMap(_.get(p.col).map {
78-
case TileCell(tile) => tile
79-
case GuardCell(_) => Tile.Empty
80-
}),
105+
p => toVectors.get(p.row).flatMap(_.get(p.col).map(_.toTile)).map((_, guard)),
81106
s = guard.pos
82107
)
83108

84-
def withGuard(store: LabWithNoGuardStore): LabStore = store.map(_.map((_, guard)))
85-
86-
def step(store: LabStore): LabStore =
87-
extractGuard(store).fold(ifEmpty = store) { oldGuard =>
88-
oldGuard
89-
.updated(store)
90-
.map(newGuard => store.seek(newGuard.pos).map(_.map((tile, _) => (tile, newGuard))))
91-
.getOrElse(store.seek(outOfBoundsPos)) // TODO: improve! 🔥🔥🔥
92-
}
93-
94-
def allDistinctGuardPositions(store: LabWithNoGuardStore): List[Pos] =
109+
def allDistinctGuardPositionsCount: Int =
95110
List
96-
.unfold(init = withGuard(store))(currentStore =>
97-
extractGuard(currentStore).map(guard => (guard.pos, step(currentStore)))
98-
)
111+
.unfold(init = toStore)(store => store.getGuard.map(guard => (guard.pos, store.afterGuardStep)))
99112
.distinct
113+
.length
100114

101115
object Lab:
102-
def parse(rows: List[String]): Option[Lab] = ??? // rows.map(_.toList)
116+
117+
def parse(rows: List[String]): Option[Lab] = for {
118+
cells <- rows.traverse(_.toList.traverse(Cell.parse))
119+
(guardPos, direction) <- findGuard(cells)
120+
} yield Lab(cells, Guard(guardPos, direction))
121+
122+
def findGuard(cells: List[List[Cell]]): Option[(Pos, Direction)] =
123+
cells
124+
.map(_.zipWithIndex)
125+
.zipWithIndex
126+
.map((row, rowIndex) => row.map((cell, colIndex) => (Pos(rowIndex, colIndex), cell)))
127+
.collectFirstSome(_.collectFirst { case (pos, GuardCell(direction)) => (pos, direction) })

src/test/scala/Day06Suite.scala

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,58 @@
11
import Day06.*
2+
import Day06.Cell.*
3+
import Day06.Tile.*
24
import Day06Suite.*
5+
import cats.syntax.all.*
36
import munit.ScalaCheckSuite
4-
import org.scalacheck.Gen
5-
import org.scalacheck.Prop.*
67

78
class Day06Suite extends ScalaCheckSuite:
89

9-
test("42 == 42"):
10-
assertEquals(42, 42)
10+
test("big input parsed to something"):
11+
assert(Lab.parse(rows = bigInput).isDefined)
12+
13+
test("small input parsed correctly"):
14+
assertEquals(
15+
Lab.parse(rows = smallInput),
16+
Lab(
17+
cells = List(
18+
List(e, e, e, e, o, e, e, e, e, e),
19+
List(e, e, e, e, e, e, e, e, e, o),
20+
List(e, e, e, e, e, e, e, e, e, e),
21+
List(e, e, o, e, e, e, e, e, e, e),
22+
List(e, e, e, e, e, e, e, o, e, e),
23+
List(e, e, e, e, e, e, e, e, e, e),
24+
List(e, o, e, e, u, e, e, e, e, e),
25+
List(e, e, e, e, e, e, e, e, o, e),
26+
List(o, e, e, e, e, e, e, e, e, e),
27+
List(e, e, e, e, e, e, o, e, e, e)
28+
),
29+
Guard(Pos(row = 6, col = 4), Direction.Up)
30+
).some
31+
)
32+
33+
test("guard will visit 41 distinct positions in small input lab"):
34+
assertEquals(Lab.parse(smallInput).map(_.allDistinctGuardPositionsCount), 41.some)
35+
36+
test("guard will visit 5_131 distinct positions in big input lab"):
37+
assertEquals(Lab.parse(bigInput).map(_.allDistinctGuardPositionsCount), 5_131.some)
1138

1239
object Day06Suite:
1340

41+
val e: Cell = TileCell(Empty)
42+
val o: Cell = TileCell(Obstruction)
43+
val u: Cell = GuardCell(Direction.Up)
44+
1445
val bigInput: List[String] = getLinesFromFile("src/test/scala/day06_input.txt")
46+
47+
val smallInput: List[String] = List(
48+
"....#.....",
49+
".........#",
50+
"..........",
51+
"..#.......",
52+
".......#..",
53+
"..........",
54+
".#..^.....",
55+
"........#.",
56+
"#.........",
57+
"......#..."
58+
)

0 commit comments

Comments
 (0)