Skip to content

Commit 94a8719

Browse files
authored
Merge pull request #10 from sraaphorst/D08
Day 8 complete and significant cleanup done.
2 parents 65a301c + d2f532e commit 94a8719

File tree

18 files changed

+356
-182
lines changed

18 files changed

+356
-182
lines changed

src/main/kotlin/common/collectionops.kt renamed to src/main/kotlin/common/collectionops/collectionops.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Advent of Code
22
// By Sebastian Raaphorst, 2024.
33

4-
package common
4+
package common.collectionops
55

66
/**
77
* Given a Collection, convert it into a map with the frequencies of each element.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package common.intpos2d
2+
3+
typealias IntPos2D = Pair<Int, Int>
4+
5+
operator fun IntPos2D.plus(other: IntPos2D) =
6+
IntPos2D(this.first + other.first, this.second + other.second)
7+
8+
operator fun IntPos2D.minus(other: IntPos2D) =
9+
IntPos2D(this.first - other.first, this.second - other.second)
10+
11+
operator fun IntPos2D.unaryMinus(): IntPos2D =
12+
IntPos2D(-this.first, -this.second)
13+
14+
operator fun Int.times(pos: IntPos2D): IntPos2D =
15+
IntPos2D(this * pos.first, this * pos.second)
16+
17+
operator fun IntPos2D.times(factor: Int) =
18+
factor * this

src/main/kotlin/common/parsing.kt renamed to src/main/kotlin/common/parsing/parsing.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Advent of Code
22
// By Sebastian Raaphorst, 2024.
33

4-
package common
4+
package common.parsing
55

6-
private val WhitespaceParser = Regex("""\s+""")
6+
val WhitespaceParser = Regex("""\s+""")
77

88
/**
99
* Parse two columns into two lists.

src/main/kotlin/day01/day01.kt

+18-15
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,34 @@
44
package day01
55

66
import common.day
7-
import common.parseColumns
7+
import common.parsing.parseColumns
88
import common.readInput
9-
import common.toFrequencyMap
9+
import common.collectionops.toFrequencyMap
1010
import kotlin.math.abs
1111

12-
fun answer1(list1: List<Int>, list2: List<Int>): Int =
13-
list1.sorted().zip(list2.sorted())
14-
.sumOf { (first, second) -> abs(first - second) }
15-
16-
17-
fun answer2(list1: List<Int>, list2: List<Int>): Int {
18-
val freqMap1 = list1.toFrequencyMap()
19-
val freqMap2 = list2.toFrequencyMap()
20-
return freqMap1.map { (elem, freq) -> elem * freq * freqMap2.getOrDefault(elem, 0) }.sum()
21-
}
12+
fun answer1(input: String): Int =
13+
parseColumns(input, String::toInt, String::toInt)
14+
.let { (list1, list2) ->
15+
list1.sorted().zip(list2.sorted())
16+
.sumOf { (first, second) -> abs(first - second) }
17+
}
18+
19+
fun answer2(input: String): Int =
20+
parseColumns(input, String::toInt, String::toInt)
21+
.let { (list1, list2) ->
22+
val freqMap1 = list1.toFrequencyMap()
23+
val freqMap2 = list2.toFrequencyMap()
24+
freqMap1.map { (elem, freq) -> elem * freq * freqMap2.getOrDefault(elem, 0) }.sum()
25+
}
2226

2327
fun main() {
2428
val input = readInput({}::class.day())
25-
val (list1, list2) = parseColumns(input, String::toInt, String::toInt)
2629

2730
println("--- Day 1: Historian Hysteria ---")
2831

2932
// Answer 1: 2031679
30-
println("Part 1: ${answer1(list1, list2)}")
33+
println("Part 1: ${answer1(input)}")
3134

3235
// Answer 2: 19678534
33-
println("Part 2: ${answer2(list1, list2)}")
36+
println("Part 2: ${answer2(input)}")
3437
}

src/main/kotlin/day02/day02.kt

+8-9
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
package day02
55

6-
import common.allListDrops
6+
import common.collectionops.allListDrops
77
import common.day
8-
import common.parseGrid
8+
import common.parsing.parseGrid
99
import common.readInput
1010

1111
private const val Lower = 1
@@ -22,22 +22,21 @@ private fun isReportAlmostSafe(report: List<Int>): Boolean =
2222
.any(::isReportSafe)
2323

2424

25-
fun answer1(reports: List<List<Int>>): Int =
26-
reports.count(::isReportSafe)
25+
fun answer1(input: String): Int =
26+
parseGrid(input, String::toInt).count(::isReportSafe)
2727

2828

29-
fun answer2(reports: List<List<Int>>): Int =
30-
reports.count(::isReportAlmostSafe)
29+
fun answer2(input: String): Int =
30+
parseGrid(input, String::toInt).count(::isReportAlmostSafe)
3131

3232
fun main() {
3333
val input = readInput({}::class.day())
34-
val reports = parseGrid(input, String::toInt)
3534

3635
println("--- Day 2: Red-Nosed Reports ---")
3736

3837
// Answer 1: 379
39-
println("Part 1: ${answer1(reports)}")
38+
println("Part 1: ${answer1(input)}")
4039

4140
// Answer 2: 430
42-
println("Part 2: ${answer2(reports)}")
41+
println("Part 2: ${answer2(input)}")
4342
}

src/main/kotlin/day04/day04.kt

+6-25
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,11 @@ package day04
55

66
import common.countSubstrings
77
import common.day
8-
import common.extractBlocks
9-
import common.getEastStrings
10-
import common.getNEStrings
11-
import common.getNWStrings
12-
import common.getNorthStrings
13-
import common.getSEStrings
14-
import common.getSWStrings
15-
import common.getSouthStrings
16-
import common.getWestStrings
8+
import common.collectionops.*
179
import common.readInput
1810

1911
private const val XMAS = "XMAS"
2012

21-
/**
22-
* Transformations to use in a wordsearch grid to look:
23-
* 1. Left-to-right
24-
* 2. Right-to-left
25-
* 3. Top-to-bottom
26-
* 4. Bottom-to-top
27-
* 5. Top-left to bottom-right
28-
* 6. Top-right to bottom-left
29-
* 7. Bottom-left to top-right
30-
* 8. Bottom-right to top-left
31-
*/
3213
private val Transforms = listOf(
3314
List<String>::getEastStrings,
3415
List<String>::getWestStrings,
@@ -72,14 +53,14 @@ private fun countXXmases(input: List<String>): Int =
7253

7354
}
7455

75-
fun answer1(input: List<String>): Int =
76-
countXmases(input)
56+
fun answer1(input: String): Int =
57+
countXmases(input.lines())
7758

78-
fun answer2(input: List<String>): Int =
79-
countXXmases(input)
59+
fun answer2(input: String): Int =
60+
countXXmases(input.lines())
8061

8162
fun main() {
82-
val input = readInput({}::class.day()).trim().lines()
63+
val input = readInput({}::class.day())
8364

8465
println("--- Day 4: Ceres Search ---")
8566

src/main/kotlin/day05/day05.kt

+64-50
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,16 @@
44
package day05
55

66
import common.day
7-
import common.middle
7+
import common.collectionops.middle
88
import common.readInput
99

10-
typealias OrderingRules = Map<Int, Set<Int>>
11-
typealias Updates = List<Int>
10+
private typealias OrderingRules = Map<Int, Set<Int>>
11+
private typealias Updates = List<Int>
1212

1313
/**
14-
* We parse the input failure rules into two distinct structures.
15-
* The entries are of the form a|b, where if b occurs before a in a list of updates,
16-
* the process should fail.
17-
*
18-
* Thus, this is parsed into a Map<Int, Set<Int>> where all the b from above comprise keys,
19-
* and any entry a1|b, a2|b, etc. form the set of values {a1, a2, ...} indicate that if an
20-
* ai occurs after b, the updates fail.
14+
* Parse the violation rules into a map where:
15+
* - Keys are the integers `b` that must not precede certain `a` values.
16+
* - Values are sets of `a` values that cause a violation if they follow the key.
2117
*/
2218
private fun parseViolationRules(input: String): OrderingRules =
2319
input.lines()
@@ -26,69 +22,87 @@ private fun parseViolationRules(input: String): OrderingRules =
2622
val (a, b) = line.trim().split('|').map(String::toInt)
2723
b to a
2824
}
29-
.groupBy(Pair<Int, Int>::first, Pair<Int, Int>::second)
30-
.mapValues { (_, b) -> b.toSet() }
31-
25+
.groupBy({ it.first }, { it.second })
26+
.mapValues { (_, values) -> values.toSet() }
3227

28+
/**
29+
* Parse updates as lists of integers from input lines.
30+
*/
3331
private fun parseUpdates(input: String): List<Updates> =
34-
input.lines()
35-
.map { line -> line.trim().split(',').map(String::toInt) }
32+
input.lines().map { line ->
33+
line.trim().split(',').map(String::toInt)
34+
}
3635

37-
fun parseViolation(input: String): Pair<OrderingRules, List<Updates>> {
36+
/**
37+
* Parse the violation rules and updates from the input string.
38+
* The input is split into two sections separated by a blank line.
39+
*/
40+
private fun parseViolation(input: String): Pair<OrderingRules, List<Updates>> {
3841
val (rulesString, updatesString) = input.split("\n\n")
39-
val rules = parseViolationRules(rulesString)
40-
val updates = parseUpdates(updatesString)
41-
return rules to updates
42+
return parseViolationRules(rulesString) to parseUpdates(updatesString)
4243
}
4344

44-
fun passesViolation(updates: Updates, violationRules: OrderingRules): Boolean =
45-
updates.fold(emptySet<Int>()) { disallowed, page ->
45+
/**
46+
* Check if a given sequence of updates passes the violation rules.
47+
* Returns `true` if no violations occur, otherwise `false`.
48+
*/
49+
private fun passesViolation(updates: Updates, violationRules: OrderingRules): Boolean {
50+
val disallowed = mutableSetOf<Int>()
51+
for (page in updates) {
4652
if (page in disallowed) return false
47-
disallowed + (violationRules[page] ?: emptySet())
48-
}.let { true }
53+
disallowed += violationRules[page] ?: emptySet()
54+
}
55+
return true
56+
}
4957

5058
/**
51-
* Idea:
52-
* Gather all elements.
53-
* While there are still elements in the remaining element set:
54-
* Keep picking the element that doesn't appear in any other violation for remaining elements
55-
* and add it to the ordering.
56-
* Remove it from the remaining element set.
59+
* Reorder the updates to satisfy violation rules.
60+
* Uses a recursive approach to build the ordering.
5761
*/
58-
fun reorder(updates: Updates, violationRules: OrderingRules): List<Int> {
62+
private fun reorder(updates: Updates, violationRules: OrderingRules): Updates {
5963
tailrec fun aux(
60-
reorder: List<Int> = emptyList(),
64+
reordered: Updates = emptyList(),
6165
remaining: Set<Int> = updates.toSet()
6266
): Updates {
63-
if (remaining.isEmpty()) return reorder
64-
val disallowed = remaining.flatMap { violationRules[it] ?: emptySet() }
67+
if (remaining.isEmpty()) return reordered
68+
val disallowed = remaining.flatMap { violationRules[it] ?: emptySet() }.toSet()
6569
val candidates = remaining - disallowed
66-
if (candidates.isEmpty()) throw RuntimeException("No candidate for reordering.")
67-
val candidate = candidates.first()
68-
return aux(reorder + candidate, remaining - candidate)
70+
val candidate = candidates.firstOrNull()
71+
?: throw RuntimeException("No candidate for reordering.")
72+
return aux(reordered + candidate, remaining - candidate)
6973
}
7074
return aux()
7175
}
7276

73-
fun answer1(violationRules: OrderingRules, updatesList: List<Updates>): Int =
74-
updatesList.filter { update -> passesViolation(update, violationRules) }
75-
.sumOf(List<Int>::middle)
76-
77-
fun answer2(violationRules: OrderingRules, updatesList: List<Updates>): Int =
78-
updatesList.filterNot { passesViolation(it, violationRules) }
79-
.map { reorder(it, violationRules) }
80-
.sumOf(List<Int>::middle)
77+
/**
78+
* Part 1: Sum the "middle" values of updates that pass the violation rules.
79+
*/
80+
fun answer1(input: String): Int =
81+
parseViolation(input).let { (violationRules, updatesList) ->
82+
updatesList
83+
.filter { passesViolation(it, violationRules) }
84+
.sumOf(List<Int>::middle)
85+
}
8186

87+
/**
88+
* Part 2: Sum the "middle" values of reordered updates that fail the violation rules.
89+
*/
90+
fun answer2(input: String): Int =
91+
parseViolation(input).let { (violationRules, updatesList) ->
92+
updatesList
93+
.filterNot { passesViolation(it, violationRules) }
94+
.map { reorder(it, violationRules) }
95+
.sumOf(List<Int>::middle)
96+
}
8297

8398
fun main() {
8499
val input = readInput({}::class.day()).trim()
85-
val (violationRules, updateList) = parseViolation(input)
86100

87101
println("--- Day 5: Print Queue ---")
88102

89-
// Answer 1: 4281
90-
println("Part 1: ${answer1(violationRules, updateList)}")
103+
// Part 1: 4281
104+
println("Part 1: ${answer1(input)}")
91105

92-
// Answer 2: 5466
93-
println("Part 2: ${answer2(violationRules, updateList)}")
94-
}
106+
// Part 2: 5466
107+
println("Part 2: ${answer2(input)}")
108+
}

0 commit comments

Comments
 (0)