Skip to content

Commit 19de663

Browse files
committed
feat: day 9
1 parent 8101f1d commit 19de663

File tree

6 files changed

+352
-2
lines changed

6 files changed

+352
-2
lines changed

src/main/kotlin/dev/mtib/aoc/AocRunner.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,12 @@ private suspend fun benchmark(
258258
} else if (average > lastSubmittedDuration) {
259259
val degradation = average - lastSubmittedDuration
260260
" degraded by ${TextColors.brightRed(degradation.toString())}"
261+
} else if (average == lastSubmittedDuration) {
262+
" ${TextColors.gray("stayed the same")}"
261263
} else {
262-
null
264+
""
263265
}
264-
} ?: ""
266+
} ?: " ${TextColors.brightBlue("new(?)")}"
265267
logger.log(
266268
puzzle
267269
) { "averaged at ${TextColors.brightWhite(average.toString())}$improvementText, report with: $styledCommand" }
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package dev.mtib.aoc.aoc24.days
2+
3+
import dev.mtib.aoc.day.AocDay
4+
import dev.mtib.aoc.util.AocLogger.Companion.logger
5+
import java.math.BigInteger
6+
import kotlin.math.min
7+
8+
object Day9: AocDay(2024, 9) {
9+
sealed class Descriptor(val size: Int)
10+
11+
private class File(
12+
val ID: Int,
13+
size: Int,
14+
): Descriptor(size)
15+
private class Free(
16+
size: Int,
17+
) : Descriptor(size)
18+
19+
private fun blockMemCompress(): BigInteger {
20+
val malloc = buildList<Descriptor> {
21+
for ((i, c) in input.withIndex()) {
22+
val isFile = i % 2 == 0
23+
val size = c - '0'
24+
if (size > 0) {
25+
if (isFile) {
26+
add(File(i/2, size))
27+
} else {
28+
add(Free(size))
29+
}
30+
}
31+
}
32+
33+
while (true) {
34+
val lastFileIndex = indexOfLast { it is File }
35+
val firstFreeIndex = indexOfFirst { it is Free }
36+
37+
if (firstFreeIndex == -1) {
38+
break
39+
}
40+
41+
val lastFile = get(lastFileIndex) as File
42+
val free = get(firstFreeIndex) as Free
43+
44+
val blocksToMove = min(lastFile.size, free.size)
45+
46+
val newFileLeft = File(lastFile.ID, blocksToMove)
47+
val newFileRight = File(lastFile.ID, lastFile.size - blocksToMove)
48+
val newFree = Free(free.size - blocksToMove)
49+
50+
if (newFree.size == 0) {
51+
removeAt(firstFreeIndex)
52+
} else {
53+
set(firstFreeIndex, newFree)
54+
}
55+
add(firstFreeIndex, newFileLeft)
56+
if (newFileRight.size > 0) {
57+
set(lastFileIndex + if (newFree.size != 0) 1 else 0, newFileRight)
58+
} else {
59+
removeAt(lastFileIndex + if (newFree.size != 0) 1 else 0)
60+
}
61+
}
62+
}
63+
64+
return buildList<Int> {
65+
for (file in malloc as List<File>) {
66+
repeat(file.size) {
67+
add(file.ID)
68+
}
69+
}
70+
}.withIndex().fold(BigInteger.ZERO) { acc, (i, v) -> (i * v).toBigInteger() + acc }
71+
}
72+
73+
private fun fileMemCompress(): BigInteger {
74+
val malloc = buildList<Descriptor> {
75+
for ((i, c) in input.withIndex()) {
76+
val isFile = i % 2 == 0
77+
val size = c - '0'
78+
if (size > 0) {
79+
if (isFile) {
80+
add(File(i/2, size))
81+
} else {
82+
add(Free(size))
83+
}
84+
}
85+
}
86+
87+
for (lastFile in filterIsInstance<File>().reversed()) {
88+
val free = withIndex().firstOrNull { it.value is Free && it.value.size >= lastFile.size } as IndexedValue<Free>?
89+
90+
if (free == null) {
91+
continue
92+
}
93+
94+
val lastFileIndex = indexOfLast { it is File && it.ID == lastFile.ID }
95+
if (lastFileIndex < free.index) {
96+
continue
97+
}
98+
99+
val beforeLastFile = getOrNull(lastFileIndex - 1)
100+
val afterLastFile = getOrNull(lastFileIndex + 1)
101+
102+
if (beforeLastFile != free.value && beforeLastFile is Free && afterLastFile is Free) {
103+
removeAt(lastFileIndex + 1)
104+
removeAt(lastFileIndex)
105+
set(lastFileIndex - 1, Free(beforeLastFile.size + lastFile.size + afterLastFile.size))
106+
} else if (beforeLastFile != free.value && beforeLastFile is Free) {
107+
removeAt(lastFileIndex)
108+
set(lastFileIndex - 1, Free(beforeLastFile.size + lastFile.size))
109+
} else if (afterLastFile is Free) {
110+
removeAt(lastFileIndex + 1)
111+
set(lastFileIndex, Free(lastFile.size + afterLastFile.size))
112+
} else {
113+
set(lastFileIndex, Free(lastFile.size))
114+
}
115+
116+
val afterFree = getOrNull(free.index + 1)
117+
118+
val newFree = Free(free.value.size - lastFile.size + if (afterFree is Free) afterFree.size else 0)
119+
if(newFree.size == 0) {
120+
removeAt(free.index)
121+
} else {
122+
set(free.index, newFree)
123+
if(afterFree is Free) {
124+
removeAt(free.index + 1)
125+
}
126+
}
127+
128+
add(free.index, lastFile)
129+
}
130+
131+
while (last() is Free) {
132+
removeLast()
133+
}
134+
}
135+
136+
return buildList<Int> {
137+
for (descriptor in malloc) {
138+
repeat(descriptor.size) {
139+
when(descriptor) {
140+
is File -> add(descriptor.ID)
141+
is Free -> {
142+
add(0)
143+
}
144+
}
145+
}
146+
}
147+
}.withIndex().fold(BigInteger.ZERO) { acc, (i, v) -> (i * v).toBigInteger() + acc }
148+
}
149+
150+
override suspend fun part1(): Any {
151+
return blockMemCompress()
152+
}
153+
154+
override suspend fun part2(): Any {
155+
return fileMemCompress()
156+
}
157+
}

src/main/kotlin/dev/mtib/aoc/day/AocDay.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,12 @@ open class AocDay(
320320

321321
suspend fun <T> withInput(input: String, block: suspend AocDay.() -> T): T {
322322
fakedInput = input
323+
_inputArray = null
323324
_inputLinesList = null
324325
_inputLinesArray = null
325326
return this.block().also {
326327
fakedInput = null
328+
_inputArray = null
327329
_inputLinesList = null
328330
_inputLinesArray = null
329331
}
@@ -343,6 +345,22 @@ open class AocDay(
343345
val input: String
344346
get() = fakedInput ?: realInput
345347

348+
private var _lineLength: Int? = null
349+
val lineLength: Int
350+
get() = _lineLength ?: input.indexOf('\n').also {
351+
_lineLength = it
352+
}
353+
private var _inputArray: CharArray? = null
354+
355+
val inputArray: CharArray
356+
get() = _inputArray ?: input.toCharArray().also {
357+
_inputArray = it
358+
}
359+
360+
fun getChar(x: Int, y: Int): Char {
361+
return inputArray[y * lineLength + x + y]
362+
}
363+
346364
private var _inputLinesList: List<String>? = null
347365
val inputLinesList: List<String>
348366
get() = _inputLinesList ?: input.lines().also {
@@ -367,5 +385,6 @@ open class AocDay(
367385
// No cheating!
368386
_inputLinesArray = null
369387
_inputLinesList = null
388+
_inputArray = null
370389
}
371390
}

src/main/kotlin/dev/mtib/aoc/util/AocDayLoader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ object AocDayLoader {
3535
Class.forName("dev.mtib.aoc.aoc24.days.Day6")
3636
Class.forName("dev.mtib.aoc.aoc24.days.Day7")
3737
Class.forName("dev.mtib.aoc.aoc24.days.Day8")
38+
Class.forName("dev.mtib.aoc.aoc24.days.Day9")
3839
}
3940

4041
val allDays: List<dev.mtib.aoc.day.AocDay> = listOf(
@@ -71,5 +72,6 @@ object AocDayLoader {
7172
dev.mtib.aoc.aoc24.days.Day6,
7273
dev.mtib.aoc.aoc24.days.Day7,
7374
dev.mtib.aoc.aoc24.days.Day8,
75+
dev.mtib.aoc.aoc24.days.Day9,
7476
)
7577
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package dev.mtib.aoc.aoc24.days
2+
3+
import io.kotest.core.spec.style.FunSpec
4+
import io.kotest.matchers.shouldBe
5+
6+
class Day9Test : FunSpec({
7+
fun String.checksum(): Long = withIndex().sumOf { (i, c) -> if (c == '.') 0L else i * (c - '0').toLong() }
8+
val snippet0 = """
9+
2333133121414131402
10+
""".trimIndent()
11+
val snippet1 = """
12+
0..111....22222
13+
""".trimIndent()
14+
val snippet2 = """
15+
00...111...2...333.44.5555.6666.777.888899
16+
""".trimIndent()
17+
val snippet3 = """
18+
0..111....22222
19+
02.111....2222.
20+
022111....222..
21+
0221112...22...
22+
02211122..2....
23+
022111222......
24+
""".trimIndent()
25+
val snippet4 = """
26+
00...111...2...333.44.5555.6666.777.888899
27+
009..111...2...333.44.5555.6666.777.88889.
28+
0099.111...2...333.44.5555.6666.777.8888..
29+
00998111...2...333.44.5555.6666.777.888...
30+
009981118..2...333.44.5555.6666.777.88....
31+
0099811188.2...333.44.5555.6666.777.8.....
32+
009981118882...333.44.5555.6666.777.......
33+
0099811188827..333.44.5555.6666.77........
34+
00998111888277.333.44.5555.6666.7.........
35+
009981118882777333.44.5555.6666...........
36+
009981118882777333644.5555.666............
37+
00998111888277733364465555.66.............
38+
0099811188827773336446555566..............
39+
""".trimIndent()
40+
context("part1") {
41+
test("doesn't throw") {
42+
try {
43+
Day9.part1()
44+
} catch (e: NotImplementedError) {
45+
// Ignore allowed exception
46+
}
47+
}
48+
test("example") {
49+
Day9.withInput(snippet0) {
50+
Day9.part1().toString() shouldBe "1928"
51+
}
52+
}
53+
test("smaller example") {
54+
Day9.withInput("12345") {
55+
Day9.part1().toString() shouldBe "022111222".withIndex().sumOf { (i, c) -> i * (c - '0') }.toString()
56+
}
57+
}
58+
test("smaller example 2") {
59+
Day9.withInput("11") {
60+
Day9.part1().toString() shouldBe "0"
61+
}
62+
}
63+
test("smaller example 3") {
64+
Day9.withInput("11112") {
65+
// expands to "0.1.22" compresses to "0212"
66+
// sum is 0*0 + 1*2 + 2*1 = 4
67+
Day9.part1().toString() shouldBe "10"
68+
}
69+
}
70+
test("smaller example 4") {
71+
Day9.withInput("111") {
72+
// expands to "01"
73+
Day9.part1().toString() shouldBe "1"
74+
}
75+
}
76+
test("smaller example 5") {
77+
Day9.withInput("101") {
78+
// expands to "01"
79+
Day9.part1().toString() shouldBe "1"
80+
}
81+
}
82+
test("smaller example 6") {
83+
Day9.withInput("201") {
84+
// expands to "001"
85+
// sum is 0*1 + 1*0 + 2*1 = 2
86+
Day9.part1().toString() shouldBe "2"
87+
}
88+
}
89+
}
90+
context("part2") {
91+
test("doesn't throw") {
92+
try {
93+
Day9.part2()
94+
} catch (e: NotImplementedError) {
95+
// Ignore allowed exception
96+
}
97+
}
98+
test("example") {
99+
Day9.withInput(snippet0) {
100+
Day9.part2().toString() shouldBe "2858"
101+
}
102+
}
103+
test("smaller example 1") {
104+
Day9.withInput("11112") {
105+
// expands to "0.1.22" compresses to "01..22"
106+
// sum is 0*0 + 1*1 + 2*. + 3*. + 4*2 + 5*2 = 19
107+
Day9.part2().toString() shouldBe "19"
108+
}
109+
}
110+
test("smaller example 2") {
111+
Day9.withInput("12112") {
112+
// expands to "0..1.22" compresses to "0221"
113+
Day9.part2().toString() shouldBe "0221".checksum().toString()
114+
}
115+
}
116+
test("smaller example 3") {
117+
Day9.withInput("13112") {
118+
// expands to "0...1.22" compresses to "0221"
119+
// sum is 0*0 + 1*2 + 2*2 + 3*1 = 9
120+
Day9.part2().toString() shouldBe "0221".checksum().toString()
121+
}
122+
}
123+
test("smaller example 4") {
124+
Day9.withInput("13212") {
125+
// expands to "0...11.22" compresses to "022.11"
126+
// sum is 0*0 + 1*2 + 2*2 + 3*0 + 4*1 + 5*1 = 13
127+
Day9.part2().toString() shouldBe "022.11".checksum().toString()
128+
}
129+
}
130+
test("smaller example 5") {
131+
Day9.withInput("1321201") {
132+
// expands to "0...11.223" compresses to "032211"
133+
// sum is 0*0 + 1*3 + 2*2 + 3*2 + 4*1 + 5*1 = 22
134+
Day9.part2().toString() shouldBe "22"
135+
}
136+
}
137+
}
138+
})

0 commit comments

Comments
 (0)