Compare commits

...
56 Commits
Author SHA1 Message Date
chrisly42 cda0dccc65 Day 12. Argh. 2025-12-15 21:29:08 +01:00
chrisly42 5e031c27cc WiP 2025-12-15 09:55:01 +01:00
chrisly42 de3b0795e3 Intermediate commit before I break stuff. 2025-12-11 15:30:13 +01:00
chrisly42 2190f9e1ac No need to have two different recursion functions for Day 11. Tidied up. 2025-12-11 08:00:08 +01:00
chrisly42 0192b33570 Day 10 still not working, but math will save me. 2025-12-11 07:25:11 +01:00
chrisly42 a69be406b1 Day 11. 2025-12-11 07:24:13 +01:00
chrisly42 7495f9e378 Still non-working part 2. But I have an idea. 2025-12-10 21:43:08 +01:00
chrisly42 ccc3fdad4c Restored deleted code from previous commit and used it to solve Part 1 much faster. 2025-12-10 18:44:40 +01:00
chrisly42 bd591263f6 Still not working, but stuff removed that was wrong. 2025-12-10 17:02:28 +01:00
chrisly42 4982f2cb3c Non working solution before I re-read the instructions regarding the light diagrams to be ignored for part 2. 2025-12-10 16:54:58 +01:00
chrisly42 6299b10b85 Day 10. Part 2 doesn't terminate in time and space yet. 2025-12-10 07:44:08 +01:00
Chris Hodges caf82dd7a9 Restored "clever" method for day 9. Not much faster, though.
Change-Id: I8ced811be7415db8281a13f1f8200192b05914e2
2025-12-09 13:06:28 +01:00
chrisly42 d2044f2d6a Day 9. Removed all smart code. 2025-12-09 08:36:22 +01:00
chrisly42 9a2a22805b Day 9. Tried to be smart, that was a failure. 2025-12-09 08:34:40 +01:00
chrisly42 909fb5c7d2 Day 8: Use long for result of part 2. X coords are > 16 bit. That it didn't overflow was pure chance. 2025-12-08 07:07:39 +01:00
chrisly42 52af74ecd5 Day 8: Use priority queue. 2025-12-08 07:04:34 +01:00
chrisly42 7a58627132 Day 8. 2025-12-08 06:58:11 +01:00
chrisly42 5fc0e8756a Day 7 :( 2025-12-07 07:41:53 +01:00
chrisly42 2b3549c84c Day 6. 2025-12-06 06:46:07 +01:00
chrisly42 128ffbb10f English... 2025-12-06 06:46:02 +01:00
chrisly42 f51f87736b Minor tweaking of Day 5. 2025-12-05 06:27:46 +01:00
chrisly42 c82509468a Day 5. 2025-12-05 06:21:02 +01:00
chrisly42 732c69df0e Day 4. 2025-12-04 06:41:19 +01:00
chrisly42 1f323bcc82 Restored missing empty line. 2025-12-03 06:48:13 +01:00
chrisly42 17c6a9baac Day 3. 2025-12-03 06:47:11 +01:00
chrisly42 ab5fd24260 Modified Downloader to wait until the time is right before attempting to download today's puzzle. 2025-12-02 23:01:42 +01:00
chrisly42 f399fcdea7 Day 2. 2025-12-02 07:04:09 +01:00
chrisly42 9a0b4af4ac Day 1. 2025-12-01 07:14:42 +01:00
chrisly42 79360efdf4 Prepare for 2025. 2025-11-28 10:50:07 +01:00
chrisly42 d49d3614c7 AoC 2015, day 6. 2024-12-25 12:07:21 +01:00
chrisly42 1a303d9632 Day 25. 2024-12-25 06:47:45 +01:00
chrisly42 e7d733846f Day 24. Solution earlier than working algorithm that does not require manual steps. 2024-12-24 14:23:31 +01:00
chrisly42 e00e002cd6 Bitset is even faster (<1 sec). 2024-12-23 16:31:11 +01:00
chrisly42 0d5809398c Revisited Day 22 for faster algorithm 2024-12-23 16:26:07 +01:00
chrisly42 277bc6cc8e Day 23. 2024-12-23 07:45:35 +01:00
chrisly42 e222975b2a Day 22 kotliniziation for more concise code. 2024-12-22 18:58:31 +01:00
chrisly42 343414cd04 Refresher on Coroutines for maximum parallel brute force for Day 22. 2024-12-22 18:42:55 +01:00
chrisly42 bc542bd29c Updated some dependencies. 2024-12-22 07:28:57 +01:00
chrisly42 0cb180ddf3 Day 22. 2024-12-22 07:03:10 +01:00
chrisly42 f494b763cf Day 21 :-( 2024-12-21 12:26:08 +01:00
chrisly42 aa0f1dad74 Day 20. 2024-12-20 07:19:34 +01:00
chrisly42 9047a97185 Day 19. 2024-12-19 06:29:19 +01:00
chrisly42 1b2332a49e Day 18. 2024-12-18 07:05:44 +01:00
chrisly42 b0e4461a7d Day 17. 2024-12-17 09:35:23 +01:00
chrisly42 76c01e01e5 Day 16. 2024-12-16 07:18:41 +01:00
chrisly42 3ee70151f1 Day 15. 2024-12-15 07:33:07 +01:00
chrisly42 0592f4a862 Day 14. 2024-12-14 07:06:53 +01:00
chrisly42 0d6bf1ca43 Day 13. Wrong bet on dynamic programming for part 1. 2024-12-13 19:47:41 +01:00
chrisly42 9e7ac0057b Even more obfuscation. 2024-12-12 21:42:19 +01:00
chrisly42 90cf5a8335 Day 12 finally. I hate you. 2024-12-12 21:37:24 +01:00
chrisly42 9a8b316122 Day 12 Part 2 not correct. 2024-12-12 08:12:56 +01:00
chrisly42 778b38b3a4 Minor kotlinization :) 2024-12-11 07:56:01 +01:00
chrisly42 34c0c213c4 Day 11. 2024-12-11 07:17:31 +01:00
chrisly42 3d17ed29c7 Unused variable. 2024-12-10 18:19:32 +01:00
Chris Hodges bc1888299b Okay okay, really last optimization on day 9, now beyond what the profiler can reliably measure. 2024-12-10 16:36:05 +01:00
Chris Hodges 4b84ce2803 Optimized day 9 for no reason, part 2 now runs in about 50ms for really_evil_input.txt. 2024-12-10 16:11:38 +01:00
33 changed files with 3268 additions and 47 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("jvm") version "1.9.20" kotlin("jvm") version "2.1.0"
} }
repositories { repositories {
@@ -8,7 +8,7 @@ repositories {
dependencies { dependencies {
implementation("com.github.kittinunf.fuel:fuel:3.0.0-alpha1") implementation("com.github.kittinunf.fuel:fuel:3.0.0-alpha1")
implementation("com.mohamedrejeb.ksoup:ksoup-html:0.2.1") implementation("com.mohamedrejeb.ksoup:ksoup-html:0.6.0")
} }
tasks { tasks {
@@ -19,6 +19,6 @@ tasks {
} }
wrapper { wrapper {
gradleVersion = "8.4" gradleVersion = "8.12"
} }
} }
+101 -1
View File
@@ -1,4 +1,5 @@
import java.lang.Long.numberOfTrailingZeros import java.lang.Long.numberOfTrailingZeros
import java.math.BigInteger
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@@ -187,7 +188,106 @@ fun gcdPositive(aIn: Long, bIn: Long): Long {
return a shl shift return a shl shift
} }
fun calcPrimeFactorsAndPhi(n: Long, primes: MutableList<Long>, allPrimes: MutableSet<Long>): Pair<List<Pair<Long, Int>>, Long> { // ax + by = gcdExtendedPositive(a, b)
fun extendedGcd(a: Long, b: Long): Pair<Long, Pair<Long, Long>> {
var old_r = a
var r = b
var old_s = 1L
var s = 0L
var old_t = 0L
var t = 1L
while (r != 0L) {
val q = old_r / r
val rtmp = old_r
old_r = r
r = rtmp - q * r
val stmp = old_s
old_s = s
s = stmp - q * s
val ttmp = old_t
old_t = t
t = ttmp - q * t
}
return old_r to (old_s to old_t)
}
fun extendedGcd(a: BigInteger, b: BigInteger): Pair<BigInteger, Pair<BigInteger, BigInteger>> {
var old_r = a
var r = b
var old_s = BigInteger.ONE
var s = BigInteger.ZERO
var old_t = BigInteger.ZERO
var t = BigInteger.ONE
while (r != BigInteger.ZERO) {
val q = old_r / r
val rtmp = old_r
old_r = r
r = rtmp - q * r
val stmp = old_s
old_s = s
s = stmp - q * s
val ttmp = old_t
old_t = t
t = ttmp - q * t
}
return old_r to (old_s to old_t)
}
fun extendedGcd(v: List<Long>): Pair<Long, List<Long>> {
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
val gcds = ArrayList<Long>(v.size)
val coeffs = ArrayList<Long>(v.size)
var (gcd, p1) = extendedGcd(v[0], v[1])
coeffs.add(p1.first)
coeffs.add(p1.second)
gcds.add(gcd)
gcds.add(gcd)
for (i in 2 until v.size) {
val (gcdnew, pi) = extendedGcd(gcd, v[i])
gcd = gcdnew
coeffs.add(pi.second)
gcds.add(gcd)
}
for (i in gcds.indices) {
if (gcds[i] != gcd) {
coeffs[i] *= gcds[i] / gcd
}
}
return gcd to coeffs
}
fun extendedGcdBigInteger(v: List<BigInteger>): Pair<BigInteger, List<BigInteger>> {
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
val gcds = ArrayList<BigInteger>(v.size)
val coeffs = ArrayList<BigInteger>(v.size)
var (gcd, p1) = extendedGcd(v[0], v[1])
coeffs.add(p1.first)
coeffs.add(p1.second)
gcds.add(gcd)
gcds.add(gcd)
for (i in 2 until v.size) {
val (gcdnew, pi) = extendedGcd(gcd, v[i])
gcd = gcdnew
coeffs.add(pi.second)
gcds.add(gcd)
}
for (i in gcds.indices) {
if (gcds[i] != gcd) {
coeffs[i] *= gcds[i] / gcd
}
}
return gcd to coeffs
}
fun calcPrimeFactorsAndPhi(
n: Long,
primes: MutableList<Long>,
allPrimes: MutableSet<Long>
): Pair<List<Pair<Long, Int>>, Long> {
val factors = ArrayList<Pair<Long, Int>>() val factors = ArrayList<Pair<Long, Int>>()
var phi = 1L var phi = 1L
var rem = n var rem = n
+17 -3
View File
@@ -75,6 +75,13 @@ class CharGrid {
val data: Array<CharArray> val data: Array<CharArray>
val bChar: Char val bChar: Char
constructor(width: Int, height: Int, borderChar: Char = ' ', fillChar: Char = borderChar) {
bChar = borderChar
this.width = width
this.height = height
data = Array(height) { CharArray(width) { fillChar } }
}
constructor(input: List<String>, borderChar: Char = ' ') { constructor(input: List<String>, borderChar: Char = ' ') {
bChar = borderChar bChar = borderChar
width = input[0].length width = input[0].length
@@ -89,10 +96,8 @@ class CharGrid {
height = inputGrid.size height = inputGrid.size
} }
@Deprecated("Use RelPos version instead")
operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar
@Deprecated("Use RelPos version instead")
operator fun set(col: Int, row: Int, newChar: Char?) { operator fun set(col: Int, row: Int, newChar: Char?) {
if (newChar != null && isInside(col, row)) data[row][col] = newChar if (newChar != null && isInside(col, row)) data[row][col] = newChar
} }
@@ -147,6 +152,9 @@ class CharGrid {
fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) = fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) =
generateGridPos().forEach { this[it] = op(this, it) } generateGridPos().forEach { this[it] = op(this, it) }
fun applyWithPos(relposes: Iterable<RelPos>, op: (grid: CharGrid, pos: RelPos) -> Char?) =
relposes.forEach { this[it] = op(this, it) }
fun apply(op: (content: Char) -> Char?) = fun apply(op: (content: Char) -> Char?) =
applyWithPos { grid: CharGrid, pos -> op(grid[pos]) } applyWithPos { grid: CharGrid, pos -> op(grid[pos]) }
@@ -196,6 +204,12 @@ class CharGrid {
fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.filter { predicate(get(c + it.dc, r + it.dr)) } relposes.filter { predicate(get(c + it.dc, r + it.dr)) }
fun matchRelative(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.filter { predicate(get(pos.translate(it))) }
fun matchAbsoluteRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.map { pos.translate(it) }.filter { predicate(get(it)) }
fun marchMatching(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = fun marchMatching(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.map { pos.translate(it) }.filter { predicate(get(it)) } relposes.map { pos.translate(it) }.filter { predicate(get(it)) }
@@ -215,7 +229,7 @@ class CharGrid {
val topBottom = CharArray(width + 2) { borderChar } val topBottom = CharArray(width + 2) { borderChar }
return CharGrid(Array(height + 2) { return CharGrid(Array(height + 2) {
if (it == 0 || it == height + 1) topBottom else if (it == 0 || it == height + 1) topBottom else
CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(it - 1, r - 1) } CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(r - 1, it - 1) }
}, borderChar) }, borderChar)
} }
+52
View File
@@ -0,0 +1,52 @@
package aoc2015
import CharGrid
import println
import readInput
/*
--- Day 6: Probably a Fire Hazard ---
https://adventofcode.com/2015/day/6
*/
fun main() {
fun part1(input: List<String>): Int {
val grid = CharGrid(1000, 1000)
input.forEach {
val (op, x1, y1, x2, y2) = "(toggle|turn off|turn on) (\\d+),(\\d+) through (\\d+),(\\d+)".toRegex().matchEntire(it)!!.destructured
for (y in y1.toInt()..y2.toInt()) {
for (x in x1.toInt()..x2.toInt()) {
grid[x, y] = when (op) {
"turn on" -> '*'
"turn off" -> ' '
"toggle" -> if (grid[x, y] == '*') ' ' else '*'
else -> throw IllegalStateException()
}
}
}
}
return grid.generateGridPos().count { grid[it] == '*' }
}
fun part2(input: List<String>): Int {
val grid = IntArray(1000 * 1000)
input.forEach {
val (op, x1, y1, x2, y2) = "(toggle|turn off|turn on) (\\d+),(\\d+) through (\\d+),(\\d+)".toRegex().matchEntire(it)!!.destructured
for (y in y1.toInt()..y2.toInt()) {
for (x in x1.toInt()..x2.toInt()) {
grid[x + y * 1000] = (grid[x + y * 1000] + when (op) {
"turn on" -> 1
"turn off" -> -1
"toggle" -> 2
else -> throw IllegalStateException()
}).coerceAtLeast(0)
}
}
}
return grid.sum()
}
val input = readInput("aoc2015/Day06")
part1(input).println()
part2(input).println()
}
+30 -34
View File
@@ -2,6 +2,7 @@ package aoc2024
import println import println
import readInput import readInput
import java.util.*
/* /*
--- Day 9: Disk Fragmenter --- --- Day 9: Disk Fragmenter ---
@@ -45,60 +46,55 @@ fun main() {
} }
fun part2(input: List<String>): Long { fun part2(input: List<String>): Long {
val diskSize = input[0].sumOf { it - '0' }
val bitmap = IntArray(diskSize) { 0 }
var pos = 0 var pos = 0
var isFile = true var isFile = true
var fileId = 0 val freeList = Array<MutableSet<Int>>(10) { TreeSet() }
val freeList = HashMap<Int, MutableList<Int>>() val fileList = ArrayList<Pair<Int, Int>>((input[0].length + 1) / 2)
val fileList = ArrayList<Pair<Int, Pair<Int, Int>>>()
val newFileList = ArrayList<Pair<Int, Pair<Int, Int>>>()
for (c in input[0]) { for (c in input[0]) {
val size = c - '0' val size = c - '0'
if (isFile) { if (isFile) {
if (size == 0) println("Narf!") fileList.add(pos to size)
fileList.add(fileId to (pos to size)) } else if (size > 0) {
fileId++ freeList[size].add(pos)
} else {
if (size > 0) {
freeList.getOrPut(size) { ArrayDeque(0) }.add(pos)
}
} }
pos += size pos += size
isFile = !isFile isFile = !isFile
} }
for (file in fileList.reversed()) { val newFileList = Array<Pair<Int, Int>?>(fileList.size) { null }
val (filePos, size) = file.second var revPos = fileList.size
var bestList: MutableList<Int>? = null for (file in fileList.asReversed()) {
val (filePos, size) = file
var bestListSize = 0 var bestListSize = 0
var bestListPos = 0
for (s in size..9) { for (s in size..9) {
val list = freeList[s] val list = freeList[s]
if (list?.isNotEmpty() == true && list[0] < filePos) { val firstPos = list.firstOrNull()
if (bestList == null || list[0] < bestList[0]) { if ((firstPos != null && firstPos < filePos) &&
bestList = list (bestListSize == 0 || firstPos < bestListPos)
) {
bestListSize = s bestListSize = s
bestListPos = firstPos
} }
} }
} if (bestListSize != 0) {
if (bestList != null) { val newPos = freeList[bestListSize].first()
val newPos = bestList.removeAt(0) freeList[bestListSize].remove(newPos)
if (bestListSize > size) { if (bestListSize > size) freeList[bestListSize - size].add(newPos + size)
val remBucket = freeList.getOrPut(bestListSize - size) { ArrayDeque(0) } newFileList[--revPos] = newPos to size
remBucket.add(newPos + size)
remBucket.sort()
}
newFileList.add(file.first to (newPos to size))
} else { } else {
newFileList.add(file) newFileList[--revPos] = file
// stop if not even a size 1 file could be fit
if (size == 1) break
} }
} }
for (file in newFileList) { while (revPos >= 0) {
for (i in 0 until file.second.second) { newFileList[revPos] = fileList[revPos]
bitmap[i + file.second.first] = file.first revPos--
} }
} // Gauß to the rescue!
return bitmap.map(Int::toLong).reduceIndexed { index, acc, i -> acc + index * i } return newFileList.mapIndexed { index, file -> index * (file!!.first.toLong() * file.second + (file.second * (file.second - 1) / 2)) }
.sum()
} }
// test if implementation meets criteria from the description, like: // test if implementation meets criteria from the description, like:
+67
View File
@@ -0,0 +1,67 @@
package aoc2024
import println
import readInput
import splitLongs
/*
--- Day 11: Plutonian Pebbles ---
https://adventofcode.com/2024/day/11
*/
fun main() {
val inlineTestInput = """
125 17
"""
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
val lookupTable = HashMap<Pair<Long, Int>, Long>()
fun fastCeilLog10(n: Long): Int {
for (i in 1 until powTenTable.size) {
if (powTenTable[i] > n) return i
}
return -1
}
fun rec(n: Long, i: Int): Long =
if (i == 0) 1 else lookupTable.getOrPut(n to i) {
if (n == 0L) {
rec(1L, i - 1)
} else {
val size = fastCeilLog10(n)
if (size and 1 == 0) {
val left = n / powTenTable[size / 2]
val right = n % powTenTable[size / 2]
rec(left, i - 1) + rec(right, i - 1)
} else {
rec(n * 2024L, i - 1)
}
}
}
fun part1(input: List<String>): Long {
val numbers = input[0].splitLongs()
return numbers.sumOf { rec(it, 25) }
}
fun part2(input: List<String>): Long {
val numbers = input[0].splitLongs()
return numbers.sumOf { rec(it, 75) }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day11_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 55312L)
//check(testInputPart2Result == 0L)
val input = readInput("aoc2024/Day11")
part1(input).println()
part2(input).println()
}
+98
View File
@@ -0,0 +1,98 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
/*
--- Day 12: Garden Groups ---
https://adventofcode.com/2024/day/12
*/
fun main() {
val inlineTestInput = """
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
"""
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
var sum = 0
for (p in grid.generateGridPos()) {
val c = grid[p]
if (c != '.') {
val areaPos = HashSet<RelPos>()
val fence = HashSet<RelPos>()
var newPos = setOf(p)
while (newPos.isNotEmpty()) {
val newSeeds = HashSet<RelPos>()
for (np in newPos) {
areaPos.add(np)
grid[np] = c.lowercaseChar()
newSeeds.addAll(grid.matchAbsoluteRelPos(np, CharGrid.PLUS_POS) { it == c })
fence.addAll(grid.matchRelative(np.dc, np.dr, CharGrid.PLUS_POS) { it.uppercaseChar() != c }
.map { RelPos(np.dc * 2 + it.dc, np.dr * 2 + it.dr) })
}
newPos = newSeeds
}
areaPos.forEach { grid[it] = '.' }
sum += areaPos.size * fence.size
}
}
return sum
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
var sum = 0
val magic = listOf(3, 5, 10, 12)
val star = CharGrid.PLUS_POS.plus(CharGrid.CROSS_POS)
for (p in grid.generateGridPos()) {
val c = grid[p]
if (c != '.') {
val areaPos = HashSet<RelPos>()
var newPos = setOf(p)
while (newPos.isNotEmpty()) {
val newSeeds = HashSet<RelPos>()
for (np in newPos) {
areaPos.add(np)
grid[np] = '.'
newSeeds.addAll(grid.matchAbsoluteRelPos(np, CharGrid.PLUS_POS) { it == c })
}
newPos = newSeeds
}
val corners = areaPos.sumOf {
val b = star.mapIndexed { i, rp -> if (areaPos.contains(it.translate(rp))) 1 shl i else 0 }.sum()
val outer = magic.count { b and it == 0 }
val inner = magic.filterIndexed { i, m -> (b and m == m) && (b and (16 shl i) == 0) }.count()
outer + inner
}
sum += areaPos.size * corners
}
}
return sum
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day12_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 1930)
check(testInputPart2Result == 1206)
val input = readInput("aoc2024/Day12")
part1(input).println()
part2(input).println()
}
+123
View File
@@ -0,0 +1,123 @@
package aoc2024
import println
import readInput
/*
--- Day 13: Claw Contraption ---
https://adventofcode.com/2024/day/13
*/
fun main() {
val inlineTestInput = """
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176
Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450
Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279
"""
fun part1(input: List<String>): Int {
var i = 0
var sum = 0
while (i < input.size) {
val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
i++
val xpos = IntArray(101 * 101)
val ypos = IntArray(101 * 101)
val solutions = ArrayList<Int>()
for (b in 1..100) {
for (a in 1..100) {
val op = (a - 1) + (b - 1) * 101
if ((xpos[op] == px) && (ypos[op] == py)) {
solutions.add(op)
}
if ((xpos[op] > px) || (ypos[op] > py)) break
xpos[a + (b - 1) * 101] = xpos[op] + ax
xpos[(a - 1) + b * 101] = xpos[op] + bx
ypos[a + (b - 1) * 101] = ypos[op] + ay
ypos[(a - 1) + b * 101] = ypos[op] + by
}
}
val minTokens = solutions.minOfOrNull { (it % 101) * 3 + (it / 101) }
sum += minTokens ?: 0
}
return sum
}
fun part2(input: List<String>): Long {
var i = 0
var sum = 0L
// 1. ax * pa + bx * pb = px
// 2. ay * pa + by * pb = py
// 3. pa * 3 + pb <- min
// 4. pa, pb in N (int > 0)
// ax * pa + bx * pb - px = ay * pa + by * pb - py
// (ax - ay) * pa + (bx - by) * pb + py - px = 0
// (ax - ay) * pa + (bx - by) * pb = px - py
while (i < input.size) {
val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() + 10000000000000L }
i++
// val t1 = ax - ay
// val t2 = bx - by
// val t3 = px - py
// Linear Diophantine equations
/* The simplest linear Diophantine equation takes the form
a*x + b*y = c
where a, b and c are given integers.
The solutions are described by the following theorem:
This Diophantine equation has a solution (where x and y are integers),
IFF c is a multiple of the greatest common divisor of a and b.
Moreover, if (x, y) is a solution, then the other solutions have the
form (x + kv, y − ku), where
- k is an arbitrary integer, and
- u and v are the quotients of a and b (respectively) by the greatest common divisor of a and b.
*/
// With our input data, all equations have exactly one solution
val pad = (px * by - py * bx)
val pan = (ax * by - ay * bx)
if (pad % pan == 0L) {
val pa = pad / pan
val pbd = (px - pa * ax)
if (pbd % bx == 0L) {
val pb = pbd / bx
sum += pa * 3 + pb
}
}
}
return sum
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day13_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 480)
//check(testInputPart2Result == 0L)
val input = readInput("aoc2024/Day13")
part1(input).println()
part2(input).println()
}
+89
View File
@@ -0,0 +1,89 @@
package aoc2024
import CharGrid
import println
import readInput
/*
--- Day 14: Restroom Redoubt ---
https://adventofcode.com/2024/day/14
*/
fun main() {
val inlineTestInput = """
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3
"""
fun part1(input: List<String>, time: Int, w: Int, h: Int): Int {
val robots = input.map { "p=(\\d+),(\\d+) v=(-?\\d+),(-?\\d+)".toRegex().find(it)!!.groupValues.drop(1).map { it.toInt() }.toIntArray() }
for (r in robots) {
r[0] = (r[0] + time * (r[2] + w)) % w
r[1] = (r[1] + time * (r[3] + h)) % h
}
val q1 = robots.count { it[0] < w / 2 && it[1] < h / 2 }
val q2 = robots.count { it[0] > w / 2 && it[1] < h / 2 }
val q3 = robots.count { it[0] < w / 2 && it[1] > h / 2 }
val q4 = robots.count { it[0] > w / 2 && it[1] > h / 2 }
return q1 * q2 * q3 * q4
}
fun part2(input: List<String>, w: Int, h: Int): Int {
val robots = input.map { "p=(\\d+),(\\d+) v=(-?\\d+),(-?\\d+)".toRegex().find(it)!!.groupValues.drop(1).map { it.toInt() }.toIntArray() }
/*
either: 1) x == w/2 -+ c, y == c c in [0..w/2]
or : 2) x == w/2, y in (w/2..h)
1) px + t * (vx + w) % w = w/2 - c; py + t * (vy + h) % h = c
px + t * (vx + w) % w = w/2 - (py + t * (vy + h) % h)
(px - py) + t * (((vx + w) % w) - ((vy + h) % h)) = w/2
2) px == w/2, py > w/2 % h
*/
val z = Array(robots.size) { IntArray(4) }
for (t in 1..w * h) {
/*val good = robots.all {
val x = ((it[0] + t * (it[2] + w)) % w).toInt()
val y = ((it[1] + t * (it[3] + h)) % h).toInt()
(y <= w / 2 && ((x >= w / 2 - y) && (x <= w / 2 + y))) || (x == w / 2 && y > w / 2)
}
if (!good) continue*/
for ((i, r) in robots.withIndex()) {
z[i][0] = ((r[0] + t * (r[2] + w)) % w).toInt()
z[i][1] = ((r[1] + t * (r[3] + h)) % h).toInt()
}
if (z.map { it[0] to it[1] }.distinct().count() < robots.size) continue
println("Solution $t")
val grid = CharGrid(w, h, '.')
z.forEach { grid[it[0], it[1]] = '*' }
grid.debug()
println()
return t
}
return 0
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day14_test")
val testInputPart1Result = part1(testInput, 100, 11, 7)
println("Part 1 Test: $testInputPart1Result")
//val testInputPart2Result = part2(testInput, 11, 7)
//println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 12)
//check(testInputPart2Result == 0)
val input = readInput("aoc2024/Day14")
part1(input, 100, 101, 103).println()
part2(input, 101, 103).println()
}
+230
View File
@@ -0,0 +1,230 @@
package aoc2024
import CharGrid
import println
import readInput
/*
--- Day 15: Warehouse Woes ---
https://adventofcode.com/2024/day/15
*/
fun main() {
val inlineTestInput2 = """
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
"""
val inlineTestInput = """
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv<v>>v<<
"""
val inlineTestInput3 = """
#######
#...#.#
#.....#
#..OO@#
#..O..#
#.....#
#######
<vv<<^^<<^^
"""
fun part1(input: List<String>): Int {
val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
val grid = CharGrid(input.take(splitp))
val inst = input.drop(splitp).joinToString("")
var (rc, rd) = grid.findMatches { it == '@' }[0]
for (m in inst) {
grid[rc, rd] = '.'
var dc = 0
var dr = 0
when (m) {
'<' -> dc = -1
'>' -> dc = 1
'^' -> dr = -1
'v' -> dr = 1
}
var s = 1
while (grid[rc + dc * s, rd + dr * s] != '.') {
if (grid[rc + dc * s, rd + dr * s] == '#') {
s = 0
break
}
s++
}
if (s > 0) {
if (s > 1) {
grid[rc + dc * s, rd + dr * s] = 'O'
}
rc += dc
rd += dr
}
grid[rc, rd] = '@'
}
return grid.generateGridPos().filter { grid[it] == 'O' }.sumOf { it.dc + it.dr * 100 }
}
fun canMove(grid: CharGrid, bc: Int, rd: Int, dr: Int): Boolean {
if ((grid[bc, rd] == '[')) {
val c1 = grid[bc, rd + dr]
val c2 = grid[bc + 1, rd + dr]
if ((c1 == '.') && (c2 == '.')) return true
if ((c1 == '#') || (c2 == '#')) return false
if ((c1 == '[')) return canMove(grid, bc, rd + dr, dr)
return (if (c1 == ']') canMove(grid, bc - 1, rd + dr, dr) else true)
&& (if (c2 == '[') canMove(grid, bc + 1, rd + dr, dr) else true)
} else if ((grid[bc, rd] == ']')) {
return canMove(grid, bc - 1, rd, dr)
} else {
throw IllegalStateException()
}
}
fun doMove(grid: CharGrid, bc: Int, rd: Int, dr: Int) {
if ((grid[bc, rd] == '[')) {
val c1 = grid[bc, rd + dr]
val c2 = grid[bc + 1, rd + dr]
if ((c1 == '[')) {
doMove(grid, bc, rd + dr, dr)
} else {
if (c1 == ']') doMove(grid, bc - 1, rd + dr, dr)
if (c2 == '[') doMove(grid, bc + 1, rd + dr, dr)
}
if ((grid[bc, rd + dr] == '.') && (grid[bc + 1, rd + dr] == '.')) {
grid[bc, rd + dr] = '['
grid[bc + 1, rd + dr] = ']'
grid[bc, rd] = '.'
grid[bc + 1, rd] = '.'
} else {
throw IllegalStateException()
}
} else if ((grid[bc, rd] == ']')) {
doMove(grid, bc - 1, rd, dr)
}
}
fun part2(input: List<String>): Int {
val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
val sgrid = CharGrid(input.take(splitp))
val grid = CharGrid(sgrid.width * 2, sgrid.height)
sgrid.generateGridPos().forEach {
when (val cc = sgrid[it]) {
'@' -> {
grid[it.dc * 2, it.dr] = cc
grid[it.dc * 2 + 1, it.dr] = '.'
}
'O' -> {
grid[it.dc * 2, it.dr] = '['
grid[it.dc * 2 + 1, it.dr] = ']'
}
else -> {
grid[it.dc * 2, it.dr] = cc
grid[it.dc * 2 + 1, it.dr] = cc
}
}
}
val inst = input.drop(splitp).joinToString("")
var (rc, rd) = grid.findMatches { it == '@' }[0]
for (m in inst) {
grid[rc, rd] = '.'
var dc = 0
var dr = 0
when (m) {
'<' -> dc = -1
'>' -> dc = 1
'^' -> dr = -1
'v' -> dr = 1
}
if (dc != 0) {
var s = 1
var bc: Char? = null
while (grid[rc + dc * s, rd] != '.') {
val cc = grid[rc + dc * s, rd]
if (cc == '#') {
s = 0
break
} else {
if (bc != null && cc == bc) {
s = 0
break
}
bc = cc
}
s++
}
if (s > 0) {
if (s > 1) {
for (p in s downTo 1) {
grid[rc + dc * p, rd] = grid[rc + dc * (p - 1), rd]
}
}
rc += dc
rd += dr
}
} else {
if (grid[rc, rd + dr] != '.') {
if (grid[rc, rd + dr] != '#') {
if (canMove(grid, rc, rd + dr, dr)) {
doMove(grid, rc, rd + dr, dr)
rc += dc
rd += dr
}
}
} else {
rc += dc
rd += dr
}
}
grid[rc, rd] = '@'
}
return grid.generateGridPos().filter { grid[it] == '[' }.sumOf { it.dc + it.dr * 100 }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
val testInput3 = inlineTestInput3.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day15_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput2)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 2028)
check(testInputPart2Result == 9021)
val input = readInput("aoc2024/Day15")
part1(input).println()
part2(input).println()
}
+162
View File
@@ -0,0 +1,162 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
/*
--- Day 16: Reindeer Maze ---
https://adventofcode.com/2024/day/16
*/
fun main() {
val inlineTestInput = """
###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############
"""
val inlineTestInput2 = """
#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################
"""
data class Node(val pos: RelPos, val dir: RelPos, val relLen: Int, val path: LinkedHashSet<Pair<RelPos, RelPos>>)
val costMap = hashMapOf(
RelPos(0, -1) to listOf(RelPos(0, -1) to 1, RelPos(1, 0) to 1001, RelPos(-1, 0) to 1001, RelPos(0, 1) to 2001),
RelPos(1, 0) to listOf(RelPos(1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(-1, 0) to 2001),
RelPos(0, 1) to listOf(RelPos(0, 1) to 1, RelPos(-1, 0) to 1001, RelPos(1, 0) to 1001, RelPos(0, -1) to 2001),
RelPos(-1, 0) to listOf(RelPos(-1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(1, 0) to 2001)
)
fun part1(input: List<String>): Int {
val grid = CharGrid(input, '#')
val queue = ArrayDeque<Node>()
val (startc, startr) = grid.findMatches { it == 'S' }[0]
val (endc, endr) = grid.findMatches { it == 'E' }[0]
val endpos = RelPos(endc, endr)
grid[startc, startr] = '.'
grid[endpos] = '.'
queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet()))
val bestCosts = HashMap<Pair<RelPos, RelPos>, Int>()
var minCost = Int.MAX_VALUE
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
val pos = node.pos
val dir = node.dir
val cost = node.relLen
val path = node.path
path.add(pos to dir)
if (pos == endpos) {
minCost = minCost.coerceAtMost(cost)
continue
}
if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) <= cost) {
continue
}
bestCosts[pos to dir] = cost
val nextDirs =
costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) }
.map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) }
queue.addAll(nextDirs)
}
return minCost
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input, '#')
val queue = ArrayDeque<Node>()
val (startc, startr) = grid.findMatches { it == 'S' }[0]
val (endc, endr) = grid.findMatches { it == 'E' }[0]
val endpos = RelPos(endc, endr)
grid[startc, startr] = '.'
grid[endpos] = '.'
queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet()))
val bestCosts = HashMap<Pair<RelPos, RelPos>, Int>()
var minCost = Int.MAX_VALUE
val bestNodes = HashSet<RelPos>()
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
val pos = node.pos
val dir = node.dir
val cost = node.relLen
val path = node.path
path.add(pos to dir)
if (pos == endpos) {
if (cost < minCost) {
bestNodes.clear()
bestNodes.addAll(path.map { it.first })
}
if (minCost == cost) {
bestNodes.addAll(path.map { it.first })
}
minCost = minCost.coerceAtMost(cost)
continue
}
if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) < cost) {
continue
}
bestCosts[pos to dir] = cost
val nextDirs =
costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) }
.map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) }
queue.addAll(nextDirs)
}
return bestNodes.size
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day16_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart1Result2 = part1(testInput2)
println("Part 1 Test 2: $testInputPart1Result2")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
val testInputPart2Result2 = part2(testInput2)
println("Part 2 Test2: $testInputPart2Result2")
check(testInputPart1Result == 7036)
check(testInputPart1Result2 == 11048)
check(testInputPart2Result == 45)
check(testInputPart2Result2 == 64)
val input = readInput("aoc2024/Day16")
part1(input).println()
part2(input).println()
}
+123
View File
@@ -0,0 +1,123 @@
package aoc2024
import println
import readInput
import splitInts
/*
--- Day 17: Chronospatial Computer ---
https://adventofcode.com/2024/day/17
*/
fun main() {
val inlineTestInput = """
Register A: 117440
Register B: 0
Register C: 0
Program: 0,3,5,4,3,0
"""
val regs = LongArray(3)
fun getComboOperand(op: Int): Long =
when (op) {
0, 1, 2, 3 -> op.toLong()
4, 5, 6 -> regs[op - 4]
else -> throw IllegalStateException()
}
fun part1(input: List<String>): String {
regs[0] = input[0].split(" ")[2].toLong()
regs[1] = input[1].split(" ")[2].toLong()
regs[2] = input[2].split(" ")[2].toLong()
val ops = input[4].split(" ")[1].splitInts(",")
val outs = ArrayList<Int>()
var pc = 0
while (pc < ops.size) {
when (ops[pc++]) {
// adv
0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// bdv
6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// cdv
7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// bxl
1 -> regs[1] = regs[1] xor ops[pc++].toLong()
// bst
2 -> regs[1] = getComboOperand(ops[pc++]) and 7
// jnz
3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
// bxc
4 -> {
regs[1] = regs[1] xor regs[2]
pc++
}
// out
5 -> outs.add((getComboOperand(ops[pc++]) and 7).toInt())
}
}
return outs.joinToString(",")
}
fun verify(rega: Long, ops: List<Int>, outs: List<Int>): Boolean {
regs[0] = rega
var pc = 0
var outpos = 0
while (pc < ops.size) {
when (ops[pc++]) {
// adv
0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// bdv
6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// cdv
7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
// bxl
1 -> regs[1] = regs[1] xor ops[pc++].toLong()
// bst
2 -> regs[1] = getComboOperand(ops[pc++]) and 7
// jnz
3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
// bxc
4 -> {
regs[1] = regs[1] xor regs[2]
pc++
}
// out
5 -> {
val o = (getComboOperand(ops[pc++]) and 7).toInt()
if (outs[outpos++] != o) return false
}
}
}
return true
}
fun rec(a: Long, p: Int, ops: List<Int>, outs: List<Int>): Long {
val partOutput = outs.take(p).reversed()
// assuming that all programs use 3 bit shifts in the last operation
for (i in 0L..7L) {
if (verify((a shl 3) or i, ops, partOutput)) {
if (p == outs.size) return (a shl 3) or i
val res = rec((a shl 3) or i, p + 1, ops, outs)
if (res != 0L) return res
}
}
return 0
}
fun part2(input: List<String>): Long {
val ops = input[4].split(" ")[1].splitInts(",")
val outs = ops.reversed()
return rec(0L, 1, ops, outs)
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day17_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val input = readInput("aoc2024/Day17")
part1(input).println()
part2(input).println()
}
+127
View File
@@ -0,0 +1,127 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
import splitInts
import java.util.*
/*
--- Day 18: RAM Run ---
https://adventofcode.com/2024/day/18
*/
fun main() {
val inlineTestInput = """
5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0
"""
data class Node(val pos: RelPos, val relLen: Int)
fun part1(input: List<String>, steps: Int, wh: Int): Int {
val posList = input.map { it.splitInts(",").toIntArray() }
val grid = CharGrid(wh, wh, '#', fillChar = '.')
posList.take(steps).forEach { grid[it[0], it[1]] = '#' }
val queue = PriorityQueue<Node>(Comparator.comparing { -it.pos.dc - it.pos.dr })
queue.add(Node(RelPos(0, 0), 0))
val bestCosts = HashMap<RelPos, Int>()
var minCost = Int.MAX_VALUE
while (queue.isNotEmpty()) {
val node = queue.remove()
val pos = node.pos
val cost = node.relLen
if (pos.dc == wh - 1 && pos.dr == wh - 1) {
minCost = minCost.coerceAtMost(cost)
continue
}
if ((bestCosts[pos] ?: Int.MAX_VALUE) < cost) {
continue
}
bestCosts[pos] = cost
queue.addAll(CharGrid.PLUS_POS
.map { pos.translate(it) }
.filter { grid[it] == '.' && (bestCosts[it] ?: Int.MAX_VALUE) > cost + 1 }
.map { Node(it, cost + 1) })
}
return minCost
}
fun part2(input: List<String>, wh: Int): RelPos {
val posList = input.map { it.splitInts(",").toIntArray() }
var lowerBound = 0
var higherBound = posList.size
do {
val grid = CharGrid(wh, wh, '#', fillChar = '.')
val boulderPos = (lowerBound + higherBound) / 2
posList.take(boulderPos).forEach { grid[it[0], it[1]] = '#' }
var foundPath = false
val queue = PriorityQueue<Node>(Comparator.comparing { -it.pos.dc - it.pos.dr })
queue.add(Node(RelPos(0, 0), 0))
val bestCosts = HashMap<RelPos, Int>()
while (queue.isNotEmpty()) {
val node = queue.remove()
val pos = node.pos
val cost = node.relLen
if (pos.dc == wh - 1 && pos.dr == wh - 1) {
foundPath = true
break
}
if ((bestCosts[pos] ?: Int.MAX_VALUE) < cost) {
continue
}
bestCosts[pos] = cost
queue.addAll(CharGrid.PLUS_POS
.map { pos.translate(it) }
.filter { grid[it] == '.' && (bestCosts[it] ?: Int.MAX_VALUE) > cost + 1 }
.map { Node(it, cost + 1) })
}
if (foundPath) {
lowerBound = boulderPos + 1
} else {
higherBound = boulderPos
}
} while (lowerBound < higherBound)
println("$lowerBound $higherBound")
return RelPos(posList[lowerBound - 1][0], posList[lowerBound - 1][1])
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day18_test")
val testInputPart1Result = part1(testInput, 12, 7)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput, 7)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 22)
check(testInputPart2Result == RelPos(6, 1))
val input = readInput("aoc2024/Day18")
part1(input, 1024, 71).println()
part2(input, 71).println()
}
+58
View File
@@ -0,0 +1,58 @@
package aoc2024
import println
import readInput
/*
--- Day 19: Linen Layout ---
https://adventofcode.com/2024/day/19
*/
fun main() {
val inlineTestInput = """
r, wr, b, g, bwu, rb, gb, br
brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb
"""
fun rec(inp: String, towels: Array<String>): Boolean =
inp.isEmpty() || towels.any { inp.startsWith(it) && rec(inp.substring(it.length), towels) }
fun part1(input: List<String>): Int {
val towels = input[0].split(", ").toTypedArray()
return input.drop(2).count { rec(it, towels) }
}
val cache = HashMap<String, Long>()
fun rec2(inp: String, towels: Array<String>): Long =
if (inp.isEmpty()) 1 else cache.getOrPut(inp) {
towels.filter { inp.startsWith(it) }.sumOf { rec2(inp.substring(it.length), towels) }
}
fun part2(input: List<String>): Long {
val towels = input[0].split(", ").toTypedArray()
return input.drop(2).sumOf { rec2(it, towels) }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day19_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 6)
check(testInputPart2Result == 16L)
val input = readInput("aoc2024/Day19")
part1(input).println()
part2(input).println()
}
+104
View File
@@ -0,0 +1,104 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
import kotlin.math.abs
/*
--- Day 20: Race Condition ---
https://adventofcode.com/2024/day/20
*/
fun main() {
val inlineTestInput = """
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""
data class Node(val pos: RelPos, val relLen: Int)
fun getPathCosts(grid: CharGrid, startPos: RelPos, endPos: RelPos): HashMap<RelPos, Int> {
val queue = ArrayDeque<Node>()
queue.add(Node(startPos, 0))
val bestCosts = HashMap<RelPos, Int>()
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
val pos = node.pos
val cost = node.relLen
bestCosts[pos] = cost
if (pos == endPos) {
break
}
queue.addAll(CharGrid.PLUS_POS
.map { pos.translate(it) }
.filter { grid[it] == '.' && !bestCosts.contains(it) }
.map { Node(it, cost + 1) })
}
return bestCosts
}
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
val start = grid.findMatchesRelPos { it == 'S' }[0]
val end = grid.findMatchesRelPos { it == 'E' }[0]
grid[end] = '.'
val cheats = listOf(
RelPos(0, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(0, 1),
RelPos(0, -2), RelPos(-2, 0), RelPos(2, 0), RelPos(0, 2),
)
val pathCosts = getPathCosts(grid, start, end)
return pathCosts.asSequence().sumOf { (pos, cost) ->
cheats.map { pos.translate(it) to (abs(it.dr) + abs(it.dc)) }
.filter { (pathCosts[it.first] ?: 0) - (cost + it.second) >= 100 }
.map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
}
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
val start = grid.findMatchesRelPos { it == 'S' }[0]
val end = grid.findMatchesRelPos { it == 'E' }[0]
grid[end] = '.'
val pathCosts = getPathCosts(grid, start, end)
// manhattan distance!
val pathCostsList = pathCosts.toList()
return pathCostsList.sumOf { (pos, cost) ->
pathCostsList.asSequence().filter { abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr) <= 20 }
.filter { (pathCosts[it.first] ?: 0) - (cost + abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr)) >= 100 }
.map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
}
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day20_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 0)
check(testInputPart2Result == 0)
val input = readInput("aoc2024/Day20")
part1(input).println()
part2(input).println()
}
+141
View File
@@ -0,0 +1,141 @@
package aoc2024
import RelPos
import println
import readInput
/*
--- Day 21: Keypad Conundrum ---
https://adventofcode.com/2024/day/21
*/
fun main() {
val inlineTestInput = """
029A
980A
179A
456A
379A
"""
val doorPadMap = hashMapOf(
'7' to RelPos(-2, -3),
'8' to RelPos(-1, -3),
'9' to RelPos(0, -3),
'4' to RelPos(-2, -2),
'5' to RelPos(-1, -2),
'6' to RelPos(0, -2),
'1' to RelPos(-2, -1),
'2' to RelPos(-1, -1),
'3' to RelPos(0, -1),
'0' to RelPos(-1, 0),
'A' to RelPos(0, 0),
)
val robotPadMap = hashMapOf(
'^' to RelPos(-1, 0),
'A' to RelPos(0, 0),
'<' to RelPos(-2, 1),
'v' to RelPos(-1, 1),
'>' to RelPos(0, 1),
)
data class PadState(
var pos: RelPos,
val padMap: Map<Char, RelPos>,
val child: PadState? = null,
val cache: MutableMap<Pair<RelPos, RelPos>, Long> = HashMap()
) {
fun getMovement(c: Char): Long {
val targetPos = padMap[c]!!
return if (child == null) {
1
} else {
val movement = cache.getOrPut(pos to targetPos) {
val child = child
var sum = 0L
if (pos.dr == 0 && targetPos.dc == -2) {
while (targetPos.dr < pos.dr) {
sum += child.getMovement('^')
pos = pos.translate(0, -1)
}
while (targetPos.dr > pos.dr) {
sum += child.getMovement('v')
pos = pos.translate(0, 1)
}
}
if (pos.dc == -2 && targetPos.dr == 0) {
while (targetPos.dc > pos.dc) {
sum += child.getMovement('>')
pos = pos.translate(1, 0)
}
}
while (targetPos.dc < pos.dc) {
sum += child.getMovement('<')
pos = pos.translate(-1, 0)
}
while (targetPos.dr > pos.dr) {
sum += child.getMovement('v')
pos = pos.translate(0, 1)
}
while (targetPos.dr < pos.dr) {
sum += child.getMovement('^')
pos = pos.translate(0, -1)
}
while (targetPos.dc > pos.dc) {
sum += child.getMovement('>')
pos = pos.translate(1, 0)
}
sum += child.getMovement('A')
sum
}
pos = targetPos
movement
}
}
}
fun part1(input: List<String>): Long {
val home = RelPos(0, 0)
val myPad = PadState(home, robotPadMap)
val robotPad2 = PadState(home, robotPadMap, myPad)
val robotPad1 = PadState(home, robotPadMap, robotPad2)
val doorPad = PadState(home, doorPadMap, robotPad1)
var sum = 0L
for (seq in input) {
val seqVal = seq.take(3).toLong()
sum += seq.sumOf { doorPad.getMovement(it) } * seqVal
}
return sum
}
fun part2(input: List<String>): Long {
val home = RelPos(0, 0)
val myPad = PadState(home, robotPadMap)
var lastPad = myPad
Array(25) { PadState(home, robotPadMap, lastPad).also { lastPad = it } }
val doorPad = PadState(home, doorPadMap, lastPad)
var sum = 0L
for (seq in input) {
val seqVal = seq.take(3).toLong()
sum += seq.sumOf { doorPad.getMovement(it) } * seqVal
}
return sum
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day21_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 126384L)
//check(testInputPart2Result == 0)
val input = readInput("aoc2024/Day21")
part1(input).println()
part2(input).println()
}
+109
View File
@@ -0,0 +1,109 @@
package aoc2024
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import println
import readInput
import java.util.*
/*
--- Day 22: Monkey Market ---
https://adventofcode.com/2024/day/22
*/
fun main() {
val inlineTestInput = """
1
10
100
2024
"""
val inlineTestInput2 = """
1
2
3
2024
"""
fun priceSequence(seed: Int) = generateSequence(seed) {
val r1 = (it xor (it shl 6)) and 0xff_ff_ff
val r2 = r1 xor (r1 shr 5)
(r2 xor (r2 shl 11)) and 0xff_ff_ff
}
fun part1(input: List<String>) =
input.map(String::toInt).sumOf { priceSequence(it).elementAt(2000).toLong() }
fun part2bruteforce(input: List<String>): Int {
val strings = input.map(String::toInt).map {
val normalString = StringBuffer(2024)
val deltaString = StringBuffer(2024)
var lastd = it % 10
priceSequence(it).take(2001).forEach {
val d = it % 10
val delta = d - lastd
normalString.append(d)
deltaString.append('j' + delta)
lastd = d
}
deltaString.toString() to normalString.toString()
}
// it's not my fault that brute force is still working
// note that there are less than 19^4 valid combinations, but we don't care
return runBlocking {
(0..(19 * 19 * 19 * 19)).map {
async(Dispatchers.Default) {
val token = StringBuffer(4)
.append('a' + (it / (19 * 19 * 19)))
.append('a' + ((it / (19 * 19)) % 19))
.append('a' + ((it / 19) % 19))
.append('a' + ((it % 19))).toString()
strings.sumOf {
val idx = it.first.indexOf(token)
if (idx >= 0) it.second[idx + 3].digitToInt() else 0
}
}
}.awaitAll().max()
}
}
fun part2(input: List<String>): Int {
val map = HashMap<Int, MutableList<Int>>()
input.map(String::toInt).forEach {
var lastd = 0
var code = 0
var countDown = 4
val seenArray = BitSet(19 * 19 * 19 * 19)
for (v in priceSequence(it).take(2001)) {
val d = v % 10
val delta = d - lastd
code = ((code * 19) % (19 * 19 * 19 * 19)) + (delta + 9)
if (--countDown < 0 && !seenArray[code]) {
map.getOrPut(code) { ArrayList() }.add(d)
seenArray[code] = true
}
lastd = d
}
}
return map.maxOf { it.value.sum() }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day22_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput2)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 37327623L)
check(testInputPart2Result == 23)
val input = readInput("aoc2024/Day22")
part1(input).println()
part2(input).println()
}
+111
View File
@@ -0,0 +1,111 @@
package aoc2024
import println
import readInput
/*
--- Day 23: LAN Party ---
https://adventofcode.com/2024/day/23
*/
fun main() {
val inlineTestInput = """
kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn
"""
fun part1(input: List<String>): Int {
val nodes = HashMap<String, MutableSet<String>>()
for (i in input) {
val (n1, n2) = i.split("-")
val e1 = nodes.getOrPut(n1) { HashSet() }
val e2 = nodes.getOrPut(n2) { HashSet() }
e1.add(n2)
e2.add(n1)
}
val threesomes = nodes
.flatMap {
it.value.flatMap { i1 ->
it.value.filter { it != i1 }.mapNotNull { i2 ->
if (nodes[i1]!!.contains(it.key) && nodes[i2]!!.contains(it.key) && nodes[i1]!!.contains(i2)) sortedSetOf(it.key, i1, i2) else null
}
}
}.map { it.joinToString("-", prefix = "-") }.distinct()
return threesomes.count { it.contains("-t") }
}
fun bronKerbosch(nodes: Map<String, Set<String>>, r: Set<String>, p: Set<String>, x: MutableSet<String>): Set<String> {
if (p.isEmpty() && x.isEmpty()) {
return r
}
var maxClique = emptySet<String>()
val ptmp = p.toMutableSet()
for (v in p) {
val res = bronKerbosch(nodes, r.plus(v), ptmp.intersect(nodes[v]!!), x.intersect(nodes[v]!!).toMutableSet())
if (res.size > maxClique.size) {
maxClique = res
}
ptmp.remove(v)
x.add(v)
}
return maxClique
}
fun part2(input: List<String>): String {
val nodes = HashMap<String, MutableSet<String>>()
for (i in input) {
val (n1, n2) = i.split("-")
val e1 = nodes.getOrPut(n1) { HashSet() }
val e2 = nodes.getOrPut(n2) { HashSet() }
e1.add(n2)
e2.add(n1)
}
val largestSet = bronKerbosch(nodes, emptySet(), nodes.keys, mutableSetOf())
return largestSet.asSequence().sorted().joinToString(",")
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day23_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 7)
check(testInputPart2Result == "co,de,ka,ta")
val input = readInput("aoc2024/Day23")
part1(input).println()
part2(input).println()
}
+345
View File
@@ -0,0 +1,345 @@
package aoc2024
import println
import readInput
import java.util.*
import kotlin.collections.ArrayDeque
/*
--- Day 24: Crossed Wires ---
https://adventofcode.com/2024/day/24
*/
fun main() {
val inlineTestInput = """
x00: 1
x01: 1
x02: 1
y00: 0
y01: 1
y02: 0
x00 AND y00 -> z00
x01 XOR y01 -> z01
x02 OR y02 -> z02
"""
val inlineTestInput2 = """
x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1
ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj
"""
data class Node(val n1: String, val n2: String, val op: Int, var target: String, var val1: Boolean? = null, var val2: Boolean? = null) {
fun output() = when (op) {
0 -> val1!! || val2!!
1 -> val1!! && val2!!
2 -> val1!! != val2!!
else -> false
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Node
return target == other.target
}
override fun hashCode(): Int {
return target.hashCode()
}
}
val nodes = ArrayList<Node>()
val nodesWaiting = HashMap<String, MutableList<Node>>()
var satisfiedNodes = ArrayList<Node>()
val zTargets = TreeSet<String>()
val zState = HashMap<String, Boolean>()
val targetParent = HashMap<String, Node>()
val initialStates = HashMap<String, Boolean>()
val zInvolvedNodes = HashMap<String, MutableSet<Node>>()
val random = kotlin.random.Random(1337)
fun readCircuit(input: List<String>) {
nodes.clear()
nodesWaiting.clear()
satisfiedNodes.clear()
zTargets.clear()
zState.clear()
targetParent.clear()
initialStates.clear()
var i = 0
while (input[i++].isNotEmpty()) {
val (n, st) = input[i - 1].split(": ")
initialStates[n] = (st == "1")
}
while (i < input.size) {
val (n1, op, n2, _, target) = input[i++].split(" ")
val opCode = when (op) {
"OR" -> 0
"AND" -> 1
"XOR" -> 2
else -> throw IllegalStateException()
}
if (target.startsWith("z")) {
zTargets.add(target)
}
val node = Node(n1, n2, opCode, target)
nodes.add(node)
targetParent[target] = node
}
}
fun outputState(target: String, output: Boolean, newSatisfiedNodes: MutableCollection<Node>) {
if (target.startsWith("z")) {
zState[target] = output
}
val targetList = nodesWaiting[target] ?: return
while (targetList.isNotEmpty()) {
val tNode = targetList.removeFirst()
if (tNode.n1 == target) {
tNode.val1 = output
if (tNode.val2 != null) {
newSatisfiedNodes.add(tNode)
}
} else {
tNode.val2 = output
if (tNode.val1 != null) {
newSatisfiedNodes.add(tNode)
}
}
}
}
fun runCircuit(): Boolean {
while (zState.size != zTargets.size) {
if (satisfiedNodes.isEmpty()) {
return false
}
val newSatisfiedNodes = ArrayList<Node>()
for (node in satisfiedNodes) {
val output = node.output()
node.val1 = null
node.val2 = null
nodesWaiting.getOrPut(node.n1) { ArrayList() }.add(node)
nodesWaiting.getOrPut(node.n2) { ArrayList() }.add(node)
outputState(node.target, output, newSatisfiedNodes)
}
satisfiedNodes = newSatisfiedNodes
}
return true
}
fun reset() {
satisfiedNodes.clear()
nodesWaiting.clear()
zState.clear()
for (node in nodes) {
node.val1 = null
node.val2 = null
nodesWaiting.getOrPut(node.n1) { ArrayDeque() }.add(node)
nodesWaiting.getOrPut(node.n2) { ArrayDeque() }.add(node)
}
}
fun setXY(n: String, value: Long) {
var v = value
for (i in 0..zTargets.size - 2) {
outputState("%s%02d".format(n, i), (v and 1L) != 0L, satisfiedNodes)
v = v shr 1
}
}
fun readZ(): Long {
var result = 0L
for (i in zTargets.reversed()) {
result *= 2
if (zState[i] == true) result++
}
return result
}
fun calcInvolvedNodes() {
zInvolvedNodes.clear()
for (target in zTargets) {
val set = HashSet<Node>()
val queue = ArrayDeque<Node>()
queue.add(targetParent[target]!!)
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
set.add(node)
val t1 = targetParent[node.n1]
val t2 = targetParent[node.n2]
if (t1 != null && !set.contains(t1)) queue.add(t1)
if (t2 != null && !set.contains(t2)) queue.add(t2)
}
zInvolvedNodes[target] = set
}
}
fun swapOutputs(t1: String, t2: String) {
val n1 = targetParent[t1]!!
val n2 = targetParent[t2]!!
n1.target = t2
n2.target = t1
targetParent[t1] = n2
targetParent[t2] = n1
}
fun part1(input: List<String>): Long {
readCircuit(input)
reset()
initialStates.forEach { outputState(it.key, it.value, satisfiedNodes) }
runCircuit()
return readZ()
}
fun checkIt(x: Long, y: Long, sum: Long, mask: Long = -1L): Boolean {
reset()
setXY("x", x)
setXY("y", y)
return runCircuit() && (readZ() and mask) == sum
}
fun countGoodBits(startbit: Int): Int {
val inv = (1L shl (zTargets.size - 1)) - 1
for (b in startbit..zTargets.size - 2) {
val tv = 1L shl b
val tvi = (1L shl (b + 1)) - 1L
if (!(checkIt(tv, 0L, tv) &&
checkIt(tvi, 0L, tvi) &&
checkIt(0L, tv, tv) &&
checkIt(0L, tvi, tvi) &&
checkIt(tv, tv, 2 * tv) &&
checkIt(tvi, tvi, 2 * tvi) &&
checkIt(inv - tvi, 0L, 0L, tvi) &&
checkIt(0L, inv - tvi, 0L, tvi)
)
) {
return b
}
for (i in 0..(1L shl ((b - 8).coerceIn(1..6)))) {
val rx = random.nextLong() and tvi
val ry = random.nextLong() and tvi
if (!checkIt(rx, ry, rx + ry, mask = tvi * 2 + 1)) return b
}
}
return zTargets.size
}
fun part2(input: List<String>): String {
readCircuit(input)
calcInvolvedNodes()
val goodGates = HashSet<Node>()
val swaps = ArrayList<Pair<String, String>>()
for (b in 0..zTargets.size - 3) {
val zb0 = zInvolvedNodes["z%02d".format(b)]!!
val zb1 = zInvolvedNodes["z%02d".format(b + 1)]!!
val zb2 = zInvolvedNodes["z%02d".format(b + 2)]!!
val tv = 1L shl b
val tvi = (1L shl (b + 1)) - 1L
val good = checkIt(tv, 0L, tv) &&
checkIt(tvi, 0L, tvi) &&
checkIt(0L, tv, tv) &&
checkIt(0L, tvi, tvi) &&
checkIt(tv, tv, 2 * tv) &&
checkIt(tvi, tvi, 2 * tvi)
if (good) {
goodGates.addAll(zb0)
//goodGates.addAll(zb1)
} else {
println("Bit error at $b")
val badList = zb0.union(zb1).union(zb2).minus(goodGates).map { it.target }.toList()
var swapped = false
out@ for (p1 in badList.indices) {
for (p2 in p1 + 1..badList.lastIndex) {
swapOutputs(badList[p1], badList[p2])
val goodBits = countGoodBits(b)
if (goodBits > b + 1) {
println("**** Successfully $goodBits swapped ${badList[p1]} with ${badList[p2]}")
swapped = true
swaps.add(badList[p1] to badList[p2])
break@out
} else {
swapOutputs(badList[p1], badList[p2])
}
}
}
if (!swapped) throw IllegalStateException("Sob!")
calcInvolvedNodes()
}
}
return swaps.flatMap { listOf(it.first, it.second) }.sorted().joinToString(",")
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day24_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart1Result2 = part1(testInput2)
println("Part 1 Test 2: $testInputPart1Result2")
// val testInputPart2Result = part2(testInput2)
// println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 4L)
check(testInputPart1Result2 == 2024L)
//check(testInputPart2Result == "z00,z01,z02,z05")
val input = readInput("aoc2024/Day24")
part1(input).println()
part2(input).println()
}
+91
View File
@@ -0,0 +1,91 @@
package aoc2024
import CharGrid
import println
import readInput
/*
--- Day 25: Code Chronicle ---
https://adventofcode.com/2024/day/25
*/
fun main() {
val inlineTestInput = """
#####
.####
.####
.####
.#.#.
.#...
.....
#####
##.##
.#.##
...##
...#.
...#.
.....
.....
#....
#....
#...#
#.#.#
#.###
#####
.....
.....
#.#..
###..
###.#
###.#
#####
.....
.....
.....
#....
#.#..
#.#.#
#####
"""
fun part1(input: List<String>): Int {
var p = 0
val topRow = IntRange(0, 4)
val locks = ArrayList<IntArray>()
val keys = ArrayList<IntArray>()
while (p < input.size) {
val grid = CharGrid(input.subList(p, p + 7))
if (topRow.all { grid[it, 0] == '#' }) {
val lock = topRow.map { rc -> (topRow.find { grid[rc, it + 1] != '#' } ?: 5) }.toIntArray()
locks.add(lock)
} else {
val key = topRow.map { rc -> 5 - (topRow.find { grid[rc, 5 - it] != '#' } ?: 5) }.toIntArray()
keys.add(key)
}
p += 8
}
return locks.sumOf { lock -> keys.count { key -> key.zip(lock).all { it.first >= it.second } } }
}
fun part2(input: List<String>): Int {
return 0
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day25_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 3)
check(testInputPart2Result == 0)
val input = readInput("aoc2024/Day25")
part1(input).println()
part2(input).println()
}
+72
View File
@@ -0,0 +1,72 @@
package aoc2025
import println
import readInput
/*
--- Day 1: Secret Entrance ---
https://adventofcode.com/2025/day/1
*/
fun main() {
val inlineTestInput = """
L68
L30
R48
L5
R60
L55
L1
L99
R14
L82
"""
fun part1(input: List<String>): Int {
var dialPos = 50
var pass = 0
for (i in input) {
dialPos = if (i.startsWith("L")) {
dialPos + 100 - i.substring(1).toInt()
} else {
dialPos + i.substring(1).toInt()
}
dialPos = dialPos % 100
if (dialPos == 0) pass++
}
return pass
}
fun part2(input: List<String>): Int {
var dialPos = 50
var pass = 0
for (i in input) {
val rot = i.substring(1).toInt()
if (rot == 0) continue
if (i.startsWith("L")) {
if (dialPos == 0) pass--
dialPos -= rot
pass += (100 - dialPos) / 100
} else {
dialPos += rot
pass += dialPos / 100
}
dialPos = if (dialPos < 0) (100 - (-dialPos % 100)) % 100 else dialPos % 100
}
return pass
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day01_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 3)
check(testInputPart2Result == 6)
val input = readInput("aoc2025/Day01")
part1(input).println()
part2(input).println()
}
+86
View File
@@ -0,0 +1,86 @@
package aoc2025
import println
import readInput
import kotlin.math.log10
/*
--- Day 2: Gift Shop ---
https://adventofcode.com/2025/day/2
*/
fun main() {
val inlineTestInput = """
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124
"""
fun part1(input: List<String>): Long {
val ranges = input[0].split(",").map { it.split("-").map { it.toLong() } }
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
var sum = 0L
for ((low, high) in ranges) {
var i = low
while (i <= high) {
val numDigits = log10(i.toDouble()).toInt() + 1
if (numDigits % 2 == 1) {
i = powTenTable[numDigits]
} else {
val firstHalf = i / powTenTable[numDigits / 2]
val matchNum = firstHalf * powTenTable[numDigits / 2] + firstHalf
if (matchNum in low..high) {
sum += matchNum
}
i = (firstHalf + 1) * powTenTable[numDigits / 2]
}
}
}
return sum
}
fun part2(input: List<String>): Long {
val ranges = input[0].split(",").map { it.split("-").map { it.toLong() } }
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
val mulElevenArray = arrayOf(
generateSequence(1L) { it * 10L + 1L }.take(20).toList().toLongArray(),
generateSequence(1L) { it * 100L + 1L }.take(10).toList().toLongArray(),
generateSequence(1L) { it * 1000L + 1L }.take(7).toList().toLongArray(),
generateSequence(1L) { it * 10000L + 1L }.take(5).toList().toLongArray(),
generateSequence(1L) { it * 100000L + 1L }.take(4).toList().toLongArray(),
generateSequence(1L) { it * 1000000L + 1L }.take(4).toList().toLongArray(),
generateSequence(1L) { it * 10000000L + 1L }.take(3).toList().toLongArray(),
generateSequence(1L) { it * 100000000L + 1L }.take(3).toList().toLongArray(),
)
var sum = 0L
for ((low, high) in ranges) {
var i = low
val illegalSet = mutableSetOf<Long>()
while (i <= high) {
val numDigits = log10(i.toDouble()).toInt() + 1
for (n in 1..numDigits / 2) {
val firstBit = i / powTenTable[numDigits - n]
val matchNum = firstBit * mulElevenArray[n - 1][(numDigits - 1) / n]
illegalSet.add(matchNum)
}
val firstHalf = i / powTenTable[numDigits / 2]
i = (firstHalf + 1) * powTenTable[numDigits / 2]
}
sum += illegalSet.filter { it in low..high }.sum()
}
return sum
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day02_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 1227775554L)
check(testInputPart2Result == 4174379265L)
val input = readInput("aoc2025/Day02")
part1(input).println()
part2(input).println()
}
+57
View File
@@ -0,0 +1,57 @@
package aoc2025
import println
import readInput
/*
--- Day 3: Lobby ---
https://adventofcode.com/2025/day/3
*/
fun main() {
val inlineTestInput = """
987654321111111
811111111111119
234234234234278
818181911112111
"""
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
fun rec(i: String, si: Int, left: Int): Long {
for (dig in 9 downTo 1) {
val dp = i.indexOf('0' + dig, si)
if (dp >= 0) {
if (left == 0) {
return dig.toLong()
} else {
val lastDigits = rec(i, dp + 1, left - 1)
if (lastDigits >= 0) return lastDigits + dig * powTenTable[left]
}
}
}
return -1L
}
fun part1(input: List<String>): Long {
return input.sumOf { rec(it, 0, 1) }
}
fun part2(input: List<String>): Long {
return input.sumOf { rec(it, 0, 11) }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day03_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 357L)
check(testInputPart2Result == 3121910778619L)
val input = readInput("aoc2025/Day03")
part1(input).println()
part2(input).println()
}
+56
View File
@@ -0,0 +1,56 @@
package aoc2025
import CharGrid
import println
import readInput
/*
--- Day 4: Printing Department ---
https://adventofcode.com/2025/day/4
*/
fun main() {
val inlineTestInput = """
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
"""
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
return matches.count()
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
var removed = 0
do {
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
grid.applyWithPos(matches) { _, _ -> 'x' }
removed += matches.size
} while (matches.isNotEmpty())
return removed
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day04_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 13)
check(testInputPart2Result == 43)
val input = readInput("aoc2025/Day04")
part1(input).println()
part2(input).println()
}
+79
View File
@@ -0,0 +1,79 @@
package aoc2025
import println
import readInput
/*
--- Day 5: Cafeteria ---
https://adventofcode.com/2025/day/5
*/
fun main() {
val inlineTestInput = """
3-5
10-14
16-20
12-18
1
5
8
11
17
32
"""
fun part1(input: List<String>): Int {
val ranges = ArrayList<LongRange>()
var freshNum = 0
for (i in input) {
if (i.contains("-")) {
val (low, high) = i.split("-").map { it.toLong() }
ranges.add(LongRange(low, high))
} else if (i.isNotBlank()) {
if (ranges.any { i.toLong() in it }) freshNum++
}
}
return freshNum
}
fun part2(input: List<String>): Long {
val ranges = ArrayList<LongRange>()
for (i in input) {
if (i.contains("-")) {
val (low, high) = i.split("-").map { it.toLong() }
ranges.add(LongRange(low, high))
}
}
val consolidated = ArrayList<LongRange>()
ranges.sortBy { it.first }
var lastLow = ranges[0].first
var lastHigh = ranges[0].last
for (r in 1..ranges.lastIndex) {
if (ranges[r].first > lastHigh) {
consolidated.add(LongRange(lastLow, lastHigh))
lastLow = ranges[r].first
lastHigh = ranges[r].last
} else {
lastHigh = lastHigh.coerceAtLeast(ranges[r].last)
}
}
consolidated.add(LongRange(lastLow, lastHigh))
return consolidated.sumOf { it.last - it.first + 1 }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day05_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 3)
check(testInputPart2Result == 14L)
val input = readInput("aoc2025/Day05")
part1(input).println()
part2(input).println()
}
+56
View File
@@ -0,0 +1,56 @@
package aoc2025
import println
import readInput
import splitLongs
/*
--- Day 6: Trash Compactor ---
https://adventofcode.com/2025/day/6
*/
fun main() {
val inlineTestInput = """
123 328 51 64
45 64 387 23
6 98 215 314
* + * +
"""
fun part1(input: List<String>): Long {
val numbers = input.dropLast(1).map { it.splitLongs().toLongArray() }.toList()
val ops = input.last().split(" ").filter(String::isNotBlank)
return ops.withIndex().sumOf { (c, op) -> if (op == "+") numbers.sumOf { it[c] } else numbers.fold(1L) { acc, row -> acc * row[c] } }
}
fun part2(input: List<String>): Long {
val numbers = ArrayList<LongArray>()
val maxLineLength = input.maxOf { it.length }
val spaceMask = BooleanArray(maxLineLength)
input.forEach { it.withIndex().filter { (_, ch) -> ch != ' ' }.forEach { (c, _) -> spaceMask[c] = true } }
val numColl = ArrayList<Long>()
for (c in 0 until maxLineLength) {
if (spaceMask[c]) numColl.add(input.dropLast(1).map { it[c] }.joinToString("").trim().toLong())
if (!spaceMask[c] || c == maxLineLength - 1) {
numbers.add(numColl.toLongArray())
numColl.clear()
}
}
return input.last().split(" ").filter(String::isNotBlank).withIndex()
.sumOf { (c, op) -> if (op == "+") numbers[c].sum() else numbers[c].fold(1L) { acc, row -> acc * row } }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day06_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 4277556L)
check(testInputPart2Result == 3263827L)
val input = readInput("aoc2025/Day06")
part1(input).println()
part2(input).println()
}
+84
View File
@@ -0,0 +1,84 @@
package aoc2025
import CharGrid
import println
import readInput
/*
--- Day 7: Laboratories ---
https://adventofcode.com/2025/day/7
*/
fun main() {
val inlineTestInput = """
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
"""
val memo = ArrayList<HashMap<Int, Long>>()
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
var splits = 0
for (row in 2 until grid.height step 2) {
for (col in 1 until grid.width - 1) {
val ch = grid[col, row - 2]
if (ch == 'S') {
if (grid[col, row] == '^') {
grid[col - 1, row] = 'S'
grid[col + 1, row] = 'S'
splits++
} else {
grid[col, row] = 'S'
}
}
}
}
return splits
}
fun rec(grid: CharGrid, col: Int, row: Int): Long {
if (row >= grid.height) return 1
return memo[row / 2].getOrPut(col) {
if (grid[col, row + 2] == '^')
rec(grid, col - 1, row + 2) + rec(grid, col + 1, row + 2)
else
rec(grid, col, row + 2)
}
}
fun part2(input: List<String>): Long {
val grid = CharGrid(input)
val col = grid.collectMatches { it == 'S' }.single().second.dc
while (memo.size <= grid.height / 2) memo.add(HashMap())
return rec(grid, col, 0)
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day07_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 21)
check(testInputPart2Result == 40L)
val input = readInput("aoc2025/Day07")
part1(input).println()
part2(input).println()
}
+123
View File
@@ -0,0 +1,123 @@
package aoc2025
import println
import readInput
import splitInts
import java.util.*
import kotlin.math.sqrt
/*
--- Day 8: Playground ---
https://adventofcode.com/2025/day/8
*/
fun main() {
val inlineTestInput = """
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689
"""
fun dist(n1: IntArray, n2: IntArray) =
sqrt(((n1[0] - n2[0]).toFloat() * (n1[0] - n2[0]) + (n1[1] - n2[1]).toFloat() * (n1[1] - n2[1]) + (n1[2] - n2[2]).toFloat() * (n1[2] - n2[2])))
fun find(parent: IntArray, n: Int): Int {
if (parent[n] != n) {
parent[n] = find(parent, parent[n])
}
return parent[n]
}
fun union(parent: IntArray, n1: Int, n2: Int): Boolean {
val r1 = find(parent, n1)
val r2 = find(parent, n2)
if (r1 != r2) {
parent[r1] = r2
return true
}
return false
}
// having an oct-tree might speed up things here, so we skip far distances
fun createDistances(nodes: Array<IntArray>): PriorityQueue<Pair<Pair<Int, Int>, Float>> {
val distances = PriorityQueue<Pair<Pair<Int, Int>, Float>>(nodes.size * nodes.size / 2, Comparator.comparing { it.second })
for (i1 in 0 until nodes.size) {
for (i2 in i1 + 1 until nodes.size) {
val d = dist(nodes[i1], nodes[i2])
distances.add((i1 to i2) to d)
}
}
return distances
}
fun part1(input: List<String>, numConnections: Int): Long {
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
val distances = createDistances(nodes)
val n = nodes.size
var numCircuits = 0
val unionFindParentArray = IntArray(n) { it }
while (distances.isNotEmpty()) {
val (n1, n2) = distances.remove().first
union(unionFindParentArray, n1, n2)
numCircuits++
if (numCircuits == numConnections) break
}
val circuitSizes = LongArray(n)
for (i in 0 until n) {
circuitSizes[find(unionFindParentArray, i)]++
}
return circuitSizes.sortedDescending().take(3).fold(1L) { acc, l -> acc * l }
}
fun part2(input: List<String>): Long {
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
val distances = createDistances(nodes)
val n = nodes.size
var numCircuits = 1
val unionFindParentArray = IntArray(n) { it }
while (distances.isNotEmpty()) {
val (n1, n2) = distances.remove().first
if (union(unionFindParentArray, n1, n2)) {
numCircuits++
if (numCircuits == n) {
return nodes[n1][0].toLong() * nodes[n2][0]
}
}
}
return -1L
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day08_test")
val testInputPart1Result = part1(testInput, 10)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 40L)
check(testInputPart2Result == 25272L)
val input = readInput("aoc2025/Day08")
part1(input, 1000).println()
part2(input).println()
}
+91
View File
@@ -0,0 +1,91 @@
package aoc2025
import println
import readInput
import splitInts
import java.util.*
import kotlin.math.abs
/*
--- Day 9: Movie Theater ---
https://adventofcode.com/2025/day/9
*/
fun main() {
val inlineTestInput = """
7,1
11,1
11,7
9,7
9,5
2,5
2,3
7,3
"""
fun part1(input: List<String>): Long {
val coords = input.map { it.splitInts(",") }
var maxArea = 0L
for (i1 in 0 until coords.size step 2) {
for (i2 in i1 + 2 until coords.size step 2) {
maxArea =
((abs(coords[i1][0] - coords[i2][0]) + 1).toLong() * (abs(coords[i1][1] - coords[i2][1]) + 1).toLong()).coerceAtLeast(
maxArea
)
}
}
return maxArea
}
fun intersectsRanges(set: TreeMap<Int, ArrayList<IntRange>>, x1: Int, y1: Int, x2: Int, y2: Int) =
set.subMap(y1 + 1, y2).values.any { it.any { it.first < x2 && it.last > x1 } }
fun part2(input: List<String>): Long {
val coords = input.map { it.splitInts(",") }
val xSpans = TreeMap<Int, ArrayList<IntRange>>()
val ySpans = TreeMap<Int, ArrayList<IntRange>>()
for (i1 in 0 until coords.size) {
val i2 = if (i1 != coords.size - 1) i1 + 1 else 0
if (coords[i1][1] == coords[i2][1]) {
xSpans.getOrPut(coords[i1][1]) { ArrayList<IntRange>() }.add(
IntRange(coords[i1][0].coerceAtMost(coords[i2][0]), coords[i1][0].coerceAtLeast(coords[i2][0]))
)
} else {
ySpans.getOrPut(coords[i1][0]) { ArrayList<IntRange>() }.add(
IntRange(coords[i1][1].coerceAtMost(coords[i2][1]), coords[i1][1].coerceAtLeast(coords[i2][1]))
)
}
}
var maxArea = 0L
for (i1 in 0 until coords.size step 2) {
for (i2 in i1 + 2 until coords.size step 2) {
val x1 = coords[i1][0].coerceAtMost(coords[i2][0])
val x2 = coords[i1][0].coerceAtLeast(coords[i2][0])
val y1 = coords[i1][1].coerceAtMost(coords[i2][1])
val y2 = coords[i1][1].coerceAtLeast(coords[i2][1])
val area = ((x2 - x1 + 1).toLong() * (y2 - y1 + 1).toLong())
if (area > maxArea &&
!(intersectsRanges(xSpans, x1, y1, x2, y2) || intersectsRanges(ySpans, y1, x1, y2, x2))
) {
maxArea = area
}
}
}
return maxArea
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day09_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 50L)
check(testInputPart2Result == 24L)
val input = readInput("aoc2025/Day09")
part1(input).println()
part2(input).println()
}
+134
View File
@@ -0,0 +1,134 @@
package aoc2025
import chineseRemainder
import extendedGcdBigInteger
import primeFactors
import primeSequence
import println
import readInput
import sieveOfErastosthenes
import splitInts
import java.math.BigInteger
/*
--- Day 10: Factory ---
https://adventofcode.com/2025/day/10
*/
fun main() {
val inlineTestInput = """
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}
"""
data class Machine(val size: Int, val target: Int, val toggles: IntArray, val joltage: IntArray)
fun parse(input: List<String>): ArrayList<Machine> {
val machines = ArrayList<Machine>()
for (i in input) {
val stuff = i.split(" ")
val machSize = stuff[0].length - 2
val target = stuff[0].removeSurrounding("[", "]")
.foldIndexed(0) { i, acc, ch -> acc + if (ch == '#') (1 shl i) else 0 }
val toggles =
stuff.drop(1).dropLast(1)
.map { it.removeSurrounding("(", ")").splitInts(",").fold(0) { acc, v -> acc + (1 shl v) } }
.toIntArray()
val joltages = stuff.last().removeSurrounding("{", "}").splitInts(",").toIntArray()
machines.add(Machine(machSize, target, toggles, joltages))
}
return machines
}
fun part1(input: List<String>): Int {
val machines = parse(input)
var sumButts = 0
for (m in machines) {
// look at which different possible sets of toggles need to be pressed to result in the target number
// pressing an even time will cancel out the effect, so only look what happens if you press once
var minPresses = Int.MAX_VALUE
for (tm in 1 until (1 shl m.toggles.size)) {
val odds = m.toggles.filterIndexed { i, v -> (1 shl i) and tm != 0 }
val result = odds.fold(0) { acc, iv -> acc xor iv }
if (result == m.target) {
minPresses = minPresses.coerceAtMost(odds.size)
}
}
sumButts += minPresses
}
return sumButts
}
fun part2(input: List<String>): Int {
val machines = parse(input)
val sieve = sieveOfErastosthenes(10000)
val primes = primeSequence(sieve).take(500).toList()
var sumButts = 0
for (m in machines) {
println()
val maxJoltage = m.joltage.max()
val numBits = 32 - maxJoltage.countLeadingZeroBits()
val bigTarget = m.joltage.foldIndexed(BigInteger.ZERO) { index, acc, i ->
acc.plus(
BigInteger.valueOf(i.toLong()).shiftLeft(numBits * index)
)
}
val bigToggles = ArrayList<BigInteger>()
for (t in m.toggles) {
var tt = t
var bigToggle = BigInteger.ZERO
var shift = 0
while (tt > 0) {
if (tt and 1 != 0) {
bigToggle += BigInteger.ONE.shiftLeft(shift)
}
shift += numBits
tt = tt shr 1
}
bigToggles.add(bigToggle)
}
// j0 * bigtoggle[0] + j1 * bigtoggle[1] + ... = bigTarget
// is a Linear Diophantine equation that can be solved with the extended Euclidean algorithm
val (gcd, coeffients) = extendedGcdBigInteger(bigToggles)
if (bigTarget % gcd != BigInteger.ZERO) throw IllegalStateException()
val factorMap = HashMap<Long, Long>()
var good = true
val primeFactors = primeFactors(bigTarget.toLong(), sieve)
for (pf in primeFactors) {
val sf = factorMap[pf]
val pr = 1000 % pf
if (sf != null && sf != pr) {
good = false
break
}
factorMap[pf] = pr
}
val reducedPairs = factorMap.map { it.value to it.key }.sortedBy { it.second }
val rx = reducedPairs.chineseRemainder()
val leastFingers = 0
println(leastFingers)
sumButts += leastFingers
}
return sumButts
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day10_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 7)
//check(testInputPart2Result == 33)
val input = readInput("aoc2025/Day10")
part1(input).println()
part2(input).println()
}
+98
View File
@@ -0,0 +1,98 @@
package aoc2025
import println
import readInput
/*
--- Day 11: Reactor ---
https://adventofcode.com/2025/day/11
*/
fun main() {
val inlineTestInput = """
aaa: you hhh
you: bbb ccc
bbb: ddd eee
ccc: ddd eee fff
ddd: ggg
eee: out
fff: out
ggg: out
hhh: ccc fff iii
iii: out
"""
val inlineTestInput2 = """
svr: aaa bbb
aaa: fft
fft: ccc
bbb: tty
tty: ccc
ccc: ddd eee
ddd: hub
hub: fff
eee: dac
dac: fff
fff: ggg hhh
ggg: out
hhh: out
"""
fun parseInput(input: List<String>): HashMap<String, MutableSet<String>> {
val children = HashMap<String, MutableSet<String>>()
for (i in input) {
val (n, chs) = i.split(": ")
val ch = chs.split(" ")
children.getOrPut(n) { HashSet() }.addAll(ch)
}
return children
}
fun rec(
children: HashMap<String, MutableSet<String>>,
p: String,
stop: String,
visited: MutableSet<String> = HashSet(),
memo: HashMap<String, Long> = HashMap()
): Long {
if (p == stop) return 1L
return memo.getOrPut(p) {
visited.add(p)
val res = children[p]?.sumOf { rec(children, it, stop, visited, memo) } ?: 0L
visited.remove(p)
res
}
}
fun part1(input: List<String>): Long {
return rec(parseInput(input), "you", "out")
}
fun part2(input: List<String>): Long {
val children = parseInput(input)
val srvToDac = rec(children, "svr", "dac")
val dacToFft = rec(children, "dac", "fft")
val fftToOut = rec(children, "fft", "out")
val srvToFft = rec(children, "svr", "fft")
val fftToDac = rec(children, "fft", "dac")
val dacToOut = rec(children, "dac", "out")
return srvToDac * dacToFft * fftToOut + srvToFft * fftToDac * dacToOut
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day11_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput2)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 5L)
check(testInputPart2Result == 2L)
val input = readInput("aoc2025/Day11")
part1(input).println()
part2(input).println()
}
+126
View File
@@ -0,0 +1,126 @@
package aoc2025
import CharGrid
import println
import readInput
import splitInts
/*
--- Day 12: Christmas Tree Farm ---
https://adventofcode.com/2025/day/12
*/
fun main() {
val inlineTestInput = """
0:
###
##.
##.
1:
###
##.
.##
2:
.##
###
##.
3:
##.
###
##.
4:
###
#..
###
5:
###
.#.
###
4x4: 0 0 0 0 2 0
12x5: 1 0 1 0 2 2
12x5: 1 0 1 0 3 2
"""
fun fits(grid: LongArray, x: Int, y: Int, present: Long) =
(grid[y] or ((7L and present) shl x) == grid[y]) &&
(grid[y + 1] or ((7L and (present shr 3)) shl x) == grid[y + 1]) &&
(grid[y + 2] or ((7L and (present shr 6)) shl x) == grid[y + 2])
fun part1(input: List<String>): Int {
var lp = 0
val presents = ArrayList<IntArray>()
// nothing of this stuff is needed :-(
while (!input[lp].contains("x")) {
var charGrid = CharGrid(input.subList(lp + 1, lp + 4))
lp += 5
val setRot = HashSet<Int>()
for (r in 0..7) {
val present = charGrid.generateGridPos().foldIndexed(0) { index, acc, pos -> acc + (if (charGrid[pos] == '#') (1 shl index) else 0) }
setRot.add(present)
if (r != 3) {
// rotate
val newGrid = charGrid.copyOf()
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dr, dc] = charGrid[dc, dr] }
charGrid = newGrid
} else {
// flip
val newGrid = charGrid.copyOf()
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dc, dr] = charGrid[dc, dr] }
charGrid = newGrid
}
}
presents.add(setRot.toIntArray())
}
var fitted = 0
val presentSizes = presents.map { it[0].countOneBits() }.toIntArray()
for (p in lp until input.size) {
val (dim, pl) = input[p].split(": ")
val (width, height) = dim.splitInts("x")
val placements = pl.splitInts().toIntArray()
val totalSize = placements.mapIndexed { i, v -> v * presentSizes[i] }.sum()
if (totalSize > width * height) {
continue
}
val totalPresents = placements.sum()
// just assume it will fit if there is enough area
if (totalPresents * 9 <= width * height) {
fitted++
continue
}
println("Oh no!")
// here the hard part would have started, but except for the example input,
// it never gets here
//val grid = LongArray(height) { (1L shl width) - 1L }
}
return fitted
}
fun part2(input: List<String>): Int {
return 0
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day12_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
//check(testInputPart1Result == 2)
check(testInputPart2Result == 0)
val input = readInput("aoc2025/Day12")
part1(input).println()
part2(input).println()
}
+24 -5
View File
@@ -4,15 +4,19 @@ import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser
import fuel.Fuel import fuel.Fuel
import fuel.method import fuel.method
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime
import java.time.Month import java.time.Month
import java.time.ZoneId import java.time.ZoneId
import java.util.* import java.util.*
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
fun main() { fun main() {
var cookie = "<insert your session cookie here or store in gradle.properties>" var cookie = "<insert your session cookie here or store in gradle.properties>"
@@ -23,7 +27,7 @@ fun main() {
cookie = props.getProperty("cookie", cookie) cookie = props.getProperty("cookie", cookie)
} }
val downloader = Downloader(2024, "aoc2024", cookie) val downloader = Downloader(2025, "aoc2025", cookie)
downloader.updateToLatest() downloader.updateToLatest()
} }
@@ -42,15 +46,16 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
if (!Files.exists(targetDir)) { if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir) Files.createDirectories(targetDir)
} }
val now = LocalDate.now(ZoneId.of("UTC-1")) val now = LocalDate.now(ZoneId.of("UTC+1"))
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, 25))) { val maxPuzzles = if (year < 2025) 25 else 12
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, maxPuzzles))) {
if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) { if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) {
now.dayOfMonth now.dayOfMonth
} else { } else {
throw IllegalStateException("You're too early.") throw IllegalStateException("You're too early.")
} }
} else { } else {
25 maxPuzzles
} }
for (day in 1..lastDay) { for (day in 1..lastDay) {
@@ -59,6 +64,20 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day)) val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day))
val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day)) val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
if (!Files.exists(inputFile)) { if (!Files.exists(inputFile)) {
if (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour < 5) {
println("Puzzle will not be available within the next hour. Skipping.")
break
}
while (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour == 5) {
println("Waiting for puzzle to become available...")
runBlocking {
if (LocalTime.now(ZoneId.of("UTC+1")).minute < 59) {
delay(1.minutes)
} else {
delay(1.seconds)
}
}
}
println("Attempting to download input for day $day") println("Attempting to download input for day $day")
val (code, data) = downloadInput(day) val (code, data) = downloadInput(day)
if (code != 200) { if (code != 200) {
@@ -156,7 +175,7 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
url = "https://adventofcode.com/$year/day/$day$suffix", url = "https://adventofcode.com/$year/day/$day$suffix",
method = "GET", method = "GET",
headers = mapOf( headers = mapOf(
"User-Agent" to "git.platon42.de/chrisly42/advent-of-code", "User-Agent" to "git.platon42.de/chrisly42/advent-of-code (chrisly@platon42.de)",
"Cookie" to "session=$sessionCookie" "Cookie" to "session=$sessionCookie"
) )
) )