Skip to content

Commit 52c8c85

Browse files
authored
Merge pull request #14 from sraaphorst/D09
Day 09 completed.
2 parents 194f2d2 + 4cb98f7 commit 52c8c85

File tree

3 files changed

+225
-2
lines changed

3 files changed

+225
-2
lines changed

src/main/kotlin/day08/day08.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
package day08
55

66
import common.aocreader.fetchAdventOfCodeInput
7-
import common.day
87
import common.intpos2d.*
9-
import common.readInput
108

119
private typealias Frequency = Char
1210
private typealias AntennaMap = Map<Frequency, Set<IntPos2D>>

src/main/kotlin/day09/day09.kt

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Advent of Code 2024, Day 09.
2+
// By Sebastian Raaphorst, 2024.
3+
4+
package day09
5+
6+
import common.aocreader.fetchAdventOfCodeInput
7+
import java.math.BigInteger
8+
9+
private typealias Range = LongRange
10+
private typealias RangeList = List<Range>
11+
12+
private fun Range.size(): Long = last - first + 1
13+
14+
private data class File(val id: Int, val blocks: RangeList)
15+
16+
private data class Filesystem(val files: List<File>, val gaps: RangeList) {
17+
fun sortAndMergeRanges(ranges: RangeList): RangeList {
18+
if (ranges.isEmpty()) return emptyList()
19+
20+
val sorted = ranges.sortedBy { it.first }
21+
return sorted.fold(mutableListOf<Range>()) { acc, range ->
22+
if (acc.isEmpty()) {
23+
acc += range
24+
} else {
25+
val lastRange = acc.last()
26+
if (range.first <= lastRange.last + 1) {
27+
acc[acc.lastIndex] = lastRange.first..maxOf(lastRange.last, range.last)
28+
} else {
29+
acc += range
30+
}
31+
}
32+
acc
33+
}
34+
}
35+
36+
private fun sortFiles(files: List<File>): List<File> =
37+
files.sortedBy { it.blocks.maxOfOrNull { block -> block.last } }
38+
39+
private fun sortFile(file: File): File =
40+
file.copy(blocks = file.blocks.sortedBy { it.last })
41+
42+
fun rearrange1(): Filesystem {
43+
tailrec fun loop(fs: Filesystem): Filesystem {
44+
val files = fs.files
45+
val gaps = fs.gaps
46+
47+
val lastFile = files.lastOrNull() ?: return fs
48+
val lastBlock = lastFile.blocks.lastOrNull() ?: return fs
49+
val firstGap = gaps.firstOrNull() ?: return fs
50+
51+
// Done if the first gap is after the last block
52+
if (firstGap.first >= lastBlock.last) return fs
53+
54+
val blockSize = lastBlock.size()
55+
val gapSize = firstGap.size()
56+
57+
val (newFiles, newGaps) = when {
58+
gapSize == blockSize -> {
59+
// Same size: Swap block and gap
60+
val updatedFile = lastFile.copy(blocks = (lastFile.blocks - listOf(lastBlock)) + listOf(firstGap))
61+
val updatedFiles = files.dropLast(1) + sortFile(updatedFile)
62+
val updatedGaps = (gaps - listOf(firstGap)) + listOf(lastBlock)
63+
updatedFiles to updatedGaps
64+
}
65+
gapSize > blockSize -> {
66+
// Gap larger than block: split gap into two
67+
val dividingPoint = firstGap.first + blockSize
68+
val subGap1 = firstGap.first until dividingPoint
69+
val subGap2 = dividingPoint..firstGap.last
70+
71+
val updatedFile = lastFile.copy(blocks = (lastFile.blocks - listOf(lastBlock)) + listOf(subGap1))
72+
val updatedFiles = files.dropLast(1) + sortFile(updatedFile)
73+
val updatedGaps = (gaps - listOf(firstGap)) + listOf(subGap2, lastBlock)
74+
updatedFiles to updatedGaps
75+
}
76+
else -> {
77+
// Block larger than gap: split block into two
78+
val dividingPoint = lastBlock.last - gapSize + 1
79+
val subBlock1 = lastBlock.first until dividingPoint
80+
val subBlock2 = dividingPoint..lastBlock.last
81+
82+
val updatedFile = lastFile.copy(blocks = (lastFile.blocks - listOf(lastBlock)) + listOf(firstGap, subBlock1))
83+
val updatedFiles = files.dropLast(1) + sortFile(updatedFile)
84+
val updatedGaps = (gaps - listOf(firstGap)) + listOf(subBlock2)
85+
updatedFiles to updatedGaps
86+
}
87+
}
88+
89+
val mergedFiles = sortFiles(newFiles).map { f -> f.copy(blocks = sortAndMergeRanges(f.blocks)) }
90+
val mergedGaps = sortAndMergeRanges(newGaps)
91+
92+
return loop(Filesystem(mergedFiles, mergedGaps))
93+
}
94+
95+
return loop(this)
96+
}
97+
98+
fun rearrange2(): Filesystem {
99+
// For each file from the end, try to move it into a suitable gap
100+
val reversed = files.asReversed()
101+
val updated = reversed.fold(this) { acc, file ->
102+
val fileRange = file.blocks.lastOrNull() ?: return@fold acc
103+
val fileRangeSize = fileRange.size()
104+
105+
// Find the first suitable gap
106+
val gap = acc.gaps.firstOrNull { g ->
107+
g.size() >= fileRangeSize && g.first < fileRange.first
108+
}
109+
110+
if (gap == null) {
111+
acc
112+
} else {
113+
val updatedFile: File
114+
val updatedGaps: RangeList
115+
val gapSize = gap.size()
116+
117+
when {
118+
fileRangeSize == gapSize -> {
119+
// Same size: just replace
120+
updatedFile = file.copy(blocks = listOf(gap))
121+
updatedGaps = acc.gaps - listOf(gap)
122+
}
123+
fileRangeSize < gapSize -> {
124+
val dividingPoint = gap.first + fileRangeSize
125+
val gap1 = gap.first until dividingPoint
126+
val gap2 = dividingPoint..gap.last
127+
updatedFile = file.copy(blocks = listOf(gap1))
128+
updatedGaps = (acc.gaps - listOf(gap)) + listOf(gap2)
129+
}
130+
else -> {
131+
// Should not happen as per the logic, but just in case
132+
updatedFile = file
133+
updatedGaps = acc.gaps
134+
}
135+
}
136+
137+
val newFiles = acc.files.map {
138+
if (it.id == file.id) updatedFile else it
139+
}
140+
Filesystem(sortFiles(newFiles), sortAndMergeRanges(updatedGaps))
141+
}
142+
}
143+
144+
return updated
145+
}
146+
147+
fun checksum(): BigInteger =
148+
files.fold(BigInteger.ZERO) { acc, (idx, blocks) ->
149+
acc + blocks.fold(BigInteger.ZERO) { acc2, range ->
150+
acc2 + (range.fold(BigInteger.ZERO) { acc3, blockPos ->
151+
acc3 + (idx * blockPos).toBigInteger()
152+
})
153+
}
154+
}
155+
156+
companion object {
157+
private fun isFile(idx: Int) = idx % 2 == 0
158+
private fun isSpace(idx: Int) = idx % 2 == 1
159+
160+
fun parse(input: String): Filesystem {
161+
val parsed = input.withIndex().fold(
162+
Triple(mutableListOf<File>(), mutableListOf<Range>(), 0L)
163+
) { (files, spaces, currBlock), (idx, ch) ->
164+
val length = ch.digitToInt()
165+
if (length > 0) {
166+
val range: Range = currBlock until (currBlock + length)
167+
when {
168+
isFile(idx) -> files.add(File(idx / 2, listOf(range)))
169+
isSpace(idx) -> spaces.add(range)
170+
}
171+
Triple(files, spaces, currBlock + length)
172+
} else {
173+
Triple(files, spaces, currBlock)
174+
}
175+
}
176+
177+
val (files, gaps, _) = parsed
178+
val fs = Filesystem(files, gaps)
179+
// Sort everything before returning
180+
return fs.copy(files = fs.sortFiles(fs.files), gaps = fs.sortAndMergeRanges(fs.gaps))
181+
}
182+
}
183+
}
184+
185+
fun answer1(input: String): BigInteger =
186+
Filesystem.parse(input).rearrange1().checksum()
187+
188+
fun answer2(input: String): BigInteger =
189+
Filesystem.parse(input).rearrange2().checksum()
190+
191+
fun main() {
192+
val input = fetchAdventOfCodeInput(2024, 9)
193+
194+
println("--- Day 9: Resonant Collinearity ---")
195+
196+
// Part 1: 6384282079460
197+
println("Part 1: ${answer1(input)}")
198+
199+
// Part 2: 6408966547049
200+
println("Part 2: ${answer2(input)}")
201+
}

src/test/kotlin/day09/day09.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Advent of Code 2024, Day 09.
2+
// By Sebastian Raaphorst, 2024.
3+
4+
package day09
5+
6+
import org.junit.jupiter.api.Test
7+
import kotlin.test.assertEquals
8+
9+
class Day09Test {
10+
private companion object {
11+
val input =
12+
"""
13+
2333133121414131402
14+
""".trimIndent().trim()
15+
}
16+
17+
@Test
18+
fun `Problem 1 example`() =
19+
assertEquals(1928.toBigInteger(), answer1(input))
20+
21+
@Test
22+
fun `Problem 2 example`() =
23+
assertEquals(2858.toBigInteger(), answer2(input))
24+
}

0 commit comments

Comments
 (0)