Skip to content

Commit 42df628

Browse files
committed
Day 5: part 1
1 parent 324dc35 commit 42df628

File tree

3 files changed

+1566
-0
lines changed

3 files changed

+1566
-0
lines changed

src/main/scala/Day05.scala

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import cats.Eq
2+
import cats.data.NonEmptyList
3+
import cats.derived.*
4+
import cats.syntax.all.*
5+
6+
object Day05:
7+
8+
case class Page(n: Int)
9+
10+
object Page:
11+
def parse(s: String): Option[Page] = s.toIntOption.map(Page.apply)
12+
13+
case class OrderRule(before: Page, after: Page) derives Eq:
14+
def reverse: OrderRule = OrderRule(before = after, after = before)
15+
16+
object OrderRule:
17+
def parse(s: String): Option[OrderRule] = s.split('|') match {
18+
case Array(before, after) => (Page.parse(before), Page.parse(after)).mapN(OrderRule.apply)
19+
case _ => None
20+
}
21+
22+
case class Update(pages: NonEmptyList[Page]):
23+
24+
def allOrderRules: List[OrderRule] = pages.toList.zip(pages.tail).map(OrderRule.apply)
25+
26+
def middlePage: Page =
27+
val midIndex = pages.length / 2
28+
pages.get(if pages.length % 2 == 0 then midIndex - 1 else midIndex).get
29+
30+
def firstViolatedOrderRules(rules: NonEmptyList[OrderRule]): Option[OrderRule] =
31+
allOrderRules.map(_.reverse).find(rules.contains_)
32+
33+
object Update:
34+
def parse(s: String): Option[Update] = s.split(',').toList.toNel.flatMap(_.traverse(Page.parse).map(Update.apply))
35+
36+
extension [A](as: List[A])
37+
def splitBySeparator(sep: A): List[List[A]] =
38+
as.reverse.foldLeft(List(List.empty[A]))((l, a) => if a == sep then List.empty :: l else (a :: l.head) :: l.tail)
39+
40+
case class Input(rules: NonEmptyList[OrderRule], updates: NonEmptyList[Update]):
41+
def correctlyOrderedUpdatesMiddlePageSum: Int =
42+
updates
43+
.collect(u => u.firstViolatedOrderRules(rules) match { case None => u })
44+
.foldMap(_.middlePage.n)
45+
46+
object Input:
47+
def parse(rows: List[String]): Option[Input] =
48+
rows.splitBySeparator("") match {
49+
case rules :: updates :: Nil =>
50+
(
51+
rules.toNel.flatMap(rulesNel => rulesNel.traverse(OrderRule.parse)),
52+
updates.toNel.flatMap(_.traverse(Update.parse))
53+
).mapN(Input.apply)
54+
case _ => None
55+
}

src/test/scala/Day05Suite.scala

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import Day05.*
2+
import Day05Suite.*
3+
import cats.data.NonEmptyList
4+
import cats.syntax.all.*
5+
import munit.ScalaCheckSuite
6+
7+
class Day05Suite extends ScalaCheckSuite:
8+
9+
test("big input parsed to something"):
10+
assert(Input.parse(bigInput).isDefined)
11+
12+
test("small input parsed to something"):
13+
assert(Input.parse(smallInput).isDefined)
14+
15+
test("List splitBySeparator example"):
16+
assertEquals(
17+
List(1, 2, 3, 100, 4, 5, 100, 6).splitBySeparator(100),
18+
List(List(1, 2, 3), List(4, 5), List(6))
19+
)
20+
21+
test("all order rules from update example"):
22+
assertEquals(
23+
Update(pages = NonEmptyList.of(1, 2, 3, 4).map(Page.apply)).allOrderRules,
24+
List(
25+
OrderRule(before = Page(1), after = Page(2)),
26+
OrderRule(before = Page(2), after = Page(3)),
27+
OrderRule(before = Page(3), after = Page(4))
28+
)
29+
)
30+
31+
test("middle page from update with even # of pages example"):
32+
assertEquals(
33+
Update(pages = NonEmptyList.of(1, 2, 3, 4).map(Page.apply)).middlePage,
34+
Page(2)
35+
)
36+
37+
test("middle page from update with odd # of pages example"):
38+
assertEquals(
39+
Update(pages = NonEmptyList.of(1, 2, 3, 4, 5).map(Page.apply)).middlePage,
40+
Page(3)
41+
)
42+
43+
test("middle page from update with only one page example"):
44+
assertEquals(
45+
Update(pages = NonEmptyList.of(1).map(Page.apply)).middlePage,
46+
Page(1)
47+
)
48+
49+
test("first violated order rule for small input's first update is None"):
50+
assertEquals(
51+
Input.parse(smallInput).flatMap(input => input.updates.head.firstViolatedOrderRules(input.rules)),
52+
None
53+
)
54+
55+
test("first violated order rule for small input's second update is None"):
56+
assertEquals(
57+
Input
58+
.parse(smallInput)
59+
.flatMap(input => input.updates.toNev.get(1).flatMap(_.firstViolatedOrderRules(input.rules))),
60+
None
61+
)
62+
63+
test("first violated order rule for small input's third update is None"):
64+
assertEquals(
65+
Input
66+
.parse(smallInput)
67+
.flatMap(input => input.updates.toNev.get(2).flatMap(_.firstViolatedOrderRules(input.rules))),
68+
None
69+
)
70+
71+
test("first violated order rule for small input's fourth update is 97|75"):
72+
assertEquals(
73+
Input
74+
.parse(smallInput)
75+
.flatMap(input => input.updates.toNev.get(3).flatMap(_.firstViolatedOrderRules(input.rules))),
76+
OrderRule(before = Page(97), after = Page(75)).some
77+
)
78+
79+
test("first violated order rule for small input's fifth update is 29|13"):
80+
assertEquals(
81+
Input
82+
.parse(smallInput)
83+
.flatMap(input => input.updates.toNev.get(4).flatMap(_.firstViolatedOrderRules(input.rules))),
84+
OrderRule(before = Page(29), after = Page(13)).some
85+
)
86+
87+
test("first violated order rule for small input's sixth update is 75|13"):
88+
assertEquals(
89+
Input
90+
.parse(smallInput)
91+
.flatMap(input => input.updates.toNev.get(5).flatMap(_.firstViolatedOrderRules(input.rules))),
92+
OrderRule(before = Page(75), after = Page(13)).some
93+
)
94+
95+
test("small input correctly ordered updates middle page sum is 143"):
96+
assertEquals(
97+
Input.parse(smallInput).map(_.correctlyOrderedUpdatesMiddlePageSum),
98+
143.some
99+
)
100+
101+
test("big input correctly ordered updates middle page sum is 5_064"):
102+
assertEquals(
103+
Input.parse(bigInput).map(_.correctlyOrderedUpdatesMiddlePageSum),
104+
5_064.some
105+
)
106+
107+
object Day05Suite:
108+
109+
val bigInput: List[String] = getLinesFromFile("src/test/scala/day05_input.txt")
110+
111+
val smallInput: List[String] = List(
112+
"47|53",
113+
"97|13",
114+
"97|61",
115+
"97|47",
116+
"75|29",
117+
"61|13",
118+
"75|53",
119+
"29|13",
120+
"97|29",
121+
"53|29",
122+
"61|53",
123+
"97|53",
124+
"61|29",
125+
"47|13",
126+
"75|47",
127+
"97|75",
128+
"47|61",
129+
"75|61",
130+
"47|29",
131+
"75|13",
132+
"53|13",
133+
"",
134+
"75,47,61,53,29",
135+
"97,61,53,29,13",
136+
"75,29,13",
137+
"75,97,47,61,53",
138+
"61,13,29",
139+
"97,13,75,29,47"
140+
)

0 commit comments

Comments
 (0)