Skip to content

Commit a981c55

Browse files
committed
Day 4: part 2
1 parent 0541651 commit a981c55

File tree

2 files changed

+101
-62
lines changed

2 files changed

+101
-62
lines changed

src/main/scala/Day04.scala

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,77 +19,92 @@ object Day04:
1919
enum WordCheckResult:
2020
case Found, NotFound
2121

22+
def toInt: Int = this match {
23+
case Found => 1
24+
case NotFound => 0
25+
}
26+
def toBoolean: Boolean = this match {
27+
case Found => true
28+
case NotFound => false
29+
}
30+
31+
object WordCheckResult:
32+
def from(b: Boolean): WordCheckResult = if b then Found else NotFound
33+
2234
case class Grid(rows: List[List[Char]]):
2335

2436
val toVectors: Vector[Vector[Char]] = rows.map(_.toVector).toVector
2537

26-
val toStore: StoreGrid = Store(
27-
p => toVectors.get(p.row).flatMap(_.get(p.col)),
28-
s = Pos.zero
29-
)
38+
val toStore: StoreGrid = Store(p => toVectors.get(p.row).flatMap(_.get(p.col)), s = Pos.zero)
3039

3140
val allPositions: List[Pos] =
3241
rows.zipWithIndex.flatMap((row, rowIndex) => row.zipWithIndex.map((_, colIndex) => Pos(rowIndex, colIndex)))
3342

43+
def allOccurrences(s: String): Int =
44+
val w = Word(s)
45+
(allWordChecks(w) ++ allWordChecks(w.reverse)).foldMap(_.toInt)
46+
3447
def allWordChecks(w: Word): List[WordCheckResult] =
3548
toStore
36-
.coflatMap(s =>
37-
List(
38-
horizontalPositions,
39-
verticalPositions,
40-
ascDiagonalPositions,
41-
descDiagonalPositions
42-
).map(wordCheck(_)(w, s))
43-
)
49+
.coflatMap(wordChecks(w, _))
4450
.experiment(_ => allPositions)
4551
.flatten
4652

47-
def allOccurrences(s: String): Int =
48-
val w = Word(s)
49-
(allWordChecks(w) ++ allWordChecks(w.reverse)).foldMap {
50-
case WordCheckResult.Found => 1
51-
case WordCheckResult.NotFound => 0
52-
}
53+
// part 2
54+
def allCrossOccurrences(s: String): Int =
55+
toStore
56+
.coflatMap(crossWordCheck(Word(s), _))
57+
.experiment(_ => allPositions)
58+
.foldMap(_.toInt)
5359

5460
object Grid:
5561

5662
def parse(rows: List[String]): Option[Grid] = Grid(rows = rows.map(_.toList)).some
5763

58-
def wordCheck(positions: Word => Pos => List[Pos])(w: Word, store: StoreGrid): WordCheckResult =
59-
if w.toOptionalChars == store.experiment(positions(w)) then WordCheckResult.Found else WordCheckResult.NotFound
60-
61-
/*
62-
XMAS
63-
....
64-
....
65-
....
66-
*/
64+
def wordChecks(w: Word, store: StoreGrid): List[WordCheckResult] =
65+
List(
66+
horizontalPositions,
67+
verticalPositions,
68+
ascDiagonalPositions,
69+
descDiagonalPositions
70+
).map(wordCheck(_, w, store))
71+
72+
def wordCheck(positions: Word => Pos => List[Pos], w: Word, store: StoreGrid): WordCheckResult =
73+
WordCheckResult.from(w.toOptionalChars == store.experiment(positions(w)))
74+
75+
/* XMAS
76+
....
77+
....
78+
.... */
6779
def horizontalPositions(w: Word)(from: Pos): List[Pos] =
6880
List.range(start = from.col, end = from.col + w.length).map(col => Pos(from.row, col))
6981

70-
/*
71-
X...
72-
M...
73-
A...
74-
S...
75-
*/
82+
/* X...
83+
M...
84+
A...
85+
S... */
7686
def verticalPositions(w: Word)(from: Pos): List[Pos] =
7787
List.range(start = from.row, end = from.row + w.length).map(row => Pos(row, from.col))
7888

79-
/*
80-
...S
81-
..A.
82-
.M..
83-
X...
84-
*/
89+
/* ...S
90+
..A.
91+
.M..
92+
X... */
8593
def ascDiagonalPositions(w: Word)(from: Pos): List[Pos] =
8694
List.range(start = 0, end = w.length).reverse.map(i => Pos(row = i + from.row, col = w.length - i - 1 + from.col))
8795

88-
/*
89-
X...
90-
.M..
91-
..A.
92-
...S
93-
*/
96+
/* X...
97+
.M..
98+
..A.
99+
...S */
94100
def descDiagonalPositions(w: Word)(from: Pos): List[Pos] =
95101
List.range(start = 0, end = w.length).map(i => Pos(row = i + from.row, col = i + from.col))
102+
103+
// part 2
104+
def crossWordCheck(w: Word, store: StoreGrid): WordCheckResult =
105+
def ascDiagonalCheck: Word => Boolean = wordCheck(ascDiagonalPositions, _, store).toBoolean
106+
def descDiagonalCheck: Word => Boolean = wordCheck(descDiagonalPositions, _, store).toBoolean
107+
WordCheckResult.from(
108+
(ascDiagonalCheck(w) || ascDiagonalCheck(w.reverse)) &&
109+
(descDiagonalCheck(w) || descDiagonalCheck(w.reverse))
110+
)

src/test/scala/Day04Suite.scala

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,31 @@ class Day04Suite extends ScalaCheckSuite:
99

1010
test("small input parsed correctly"):
1111
assertEquals(
12-
Grid.parse(rows = List("abc", "def")),
13-
Grid(rows = List(List('a', 'b', 'c'), List('d', 'e', 'f'))).some
12+
Grid.parse(
13+
rows = List(
14+
"abc",
15+
"def"
16+
)
17+
),
18+
Grid(
19+
rows = List(
20+
List('a', 'b', 'c'),
21+
List('d', 'e', 'f')
22+
)
23+
).some
1424
)
1525

1626
test("big input parsed to something"):
1727
assert(Grid.parse(rows = bigInput).isDefined)
1828

1929
test("Grid allPositions example"):
2030
assertEquals(
21-
Grid(rows = List(List('a', 'b', 'c'), List('d', 'e', 'f'))).allPositions,
31+
Grid(
32+
rows = List(
33+
List('a', 'b', 'c'),
34+
List('d', 'e', 'f')
35+
)
36+
).allPositions,
2237
List(
2338
Pos(row = 0, col = 0),
2439
Pos(row = 0, col = 1),
@@ -72,28 +87,37 @@ class Day04Suite extends ScalaCheckSuite:
7287
)
7388
)
7489

75-
test("small input contains 18 occurrences of \"XMAS\""):
76-
val smallInput = List(
77-
"MMMSXXMASM",
78-
"MSAMXMSMSA",
79-
"AMXSXMAAMM",
80-
"MSAMASMSMX",
81-
"XMASAMXAMM",
82-
"XXAMMXXAMA",
83-
"SMSMSASXSS",
84-
"SAXAMASAAA",
85-
"MAMMMXMMMM",
86-
"MXMXAXMASX"
87-
)
90+
test("small input contains 18 \"XMAS\" occurrences"):
8891
assertEquals(Grid.parse(smallInput).map(_.allOccurrences("XMAS")), 18.some)
8992

90-
test("big input contains 2_578 occurrences of \"XMAS\""):
93+
test("big input contains 2_578 \"XMAS\" occurrences"):
9194
assertEquals(Grid.parse(bigInput).map(_.allOccurrences("XMAS")), 2_578.some)
9295

96+
// part 2
97+
98+
test("small input contains 9 cross-\"MAS\" occurrences"):
99+
assertEquals(Grid.parse(smallInput).map(_.allCrossOccurrences("MAS")), 9.some)
100+
101+
test("big input contains 1_972 cross-\"MAS\" occurrences"):
102+
assertEquals(Grid.parse(bigInput).map(_.allCrossOccurrences("MAS")), 1_972.some)
103+
93104
object Day04Suite:
94105

95106
val bigInput: List[String] = getLinesFromFile("src/test/scala/day04_input.txt")
96107

108+
val smallInput = List(
109+
"MMMSXXMASM",
110+
"MSAMXMSMSA",
111+
"AMXSXMAAMM",
112+
"MSAMASMSMX",
113+
"XMASAMXAMM",
114+
"XXAMMXXAMA",
115+
"SMSMSASXSS",
116+
"SAXAMASAAA",
117+
"MAMMMXMMMM",
118+
"MXMXAXMASX"
119+
)
120+
97121
val nonEmptyGridGen: Gen[Grid] = Gen.zip(Gen.choose(1, 10), Gen.choose(1, 10)).flatMap { (r, c) =>
98122
Gen.listOfN(r, Gen.listOfN(c, Gen.alphaUpperChar)).map(Grid.apply)
99123
}

0 commit comments

Comments
 (0)