Compare commits

..
58 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
chrisly42 abb0225d06 Day 10. 2024-12-10 06:26:53 +01:00
chrisly42 89b897b2c1 Day 9. 2024-12-09 06:56:54 +01:00
34 changed files with 3421 additions and 12 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "1.9.20"
kotlin("jvm") version "2.1.0"
}
repositories {
@@ -8,7 +8,7 @@ repositories {
dependencies {
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 {
@@ -19,6 +19,6 @@ tasks {
}
wrapper {
gradleVersion = "8.4"
gradleVersion = "8.12"
}
}
+101 -1
View File
@@ -1,4 +1,5 @@
import java.lang.Long.numberOfTrailingZeros
import java.math.BigInteger
import java.util.*
import kotlin.math.abs
import kotlin.math.min
@@ -187,7 +188,106 @@ fun gcdPositive(aIn: Long, bIn: Long): Long {
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>>()
var phi = 1L
var rem = n
+24 -3
View File
@@ -75,6 +75,13 @@ class CharGrid {
val data: Array<CharArray>
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 = ' ') {
bChar = borderChar
width = input[0].length
@@ -89,10 +96,8 @@ class CharGrid {
height = inputGrid.size
}
@Deprecated("Use RelPos version instead")
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?) {
if (newChar != null && isInside(col, row)) data[row][col] = newChar
}
@@ -147,6 +152,9 @@ class CharGrid {
fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) =
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?) =
applyWithPos { grid: CharGrid, pos -> op(grid[pos]) }
@@ -196,10 +204,23 @@ class CharGrid {
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)) }
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> =
relposes.map { pos.translate(it) }.filter { predicate(get(it)) }
fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList()
}
fun findMatchesRelPos(predicate: (char: Char) -> Boolean): List<RelPos> {
return generateGridPos().filter { predicate(get(it)) }.toList()
}
fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> {
return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList()
}
@@ -208,7 +229,7 @@ class CharGrid {
val topBottom = CharArray(width + 2) { borderChar }
return CharGrid(Array(height + 2) {
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)
}
+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()
}
+113
View File
@@ -0,0 +1,113 @@
package aoc2024
import println
import readInput
import java.util.*
/*
--- Day 9: Disk Fragmenter ---
https://adventofcode.com/2024/day/9
*/
fun main() {
val inlineTestInput = """2333133121414131402"""
fun part1(input: List<String>): Long {
val diskSize = input[0].sumOf { it - '0' }
val bitmap = IntArray(diskSize) { -1 }
var pos = 0
var isFile = true
var fileId = 0
var usedBlocks = 0
for (c in input[0]) {
val size = c - '0'
if (isFile) {
usedBlocks += size
for (p in 1..size) {
bitmap[pos++] = fileId
}
fileId++
} else {
pos += size
}
isFile = !isFile
}
var backPos = diskSize - 1
for (i in 0 until usedBlocks) {
if (bitmap[i] == -1) {
while (bitmap[backPos] < 0) {
backPos--
}
bitmap[i] = bitmap[backPos]
bitmap[backPos--] = -1
}
}
return bitmap.take(usedBlocks).map(Int::toLong).reduceIndexed { index, acc, i -> acc + index * i }
}
fun part2(input: List<String>): Long {
var pos = 0
var isFile = true
val freeList = Array<MutableSet<Int>>(10) { TreeSet() }
val fileList = ArrayList<Pair<Int, Int>>((input[0].length + 1) / 2)
for (c in input[0]) {
val size = c - '0'
if (isFile) {
fileList.add(pos to size)
} else if (size > 0) {
freeList[size].add(pos)
}
pos += size
isFile = !isFile
}
val newFileList = Array<Pair<Int, Int>?>(fileList.size) { null }
var revPos = fileList.size
for (file in fileList.asReversed()) {
val (filePos, size) = file
var bestListSize = 0
var bestListPos = 0
for (s in size..9) {
val list = freeList[s]
val firstPos = list.firstOrNull()
if ((firstPos != null && firstPos < filePos) &&
(bestListSize == 0 || firstPos < bestListPos)
) {
bestListSize = s
bestListPos = firstPos
}
}
if (bestListSize != 0) {
val newPos = freeList[bestListSize].first()
freeList[bestListSize].remove(newPos)
if (bestListSize > size) freeList[bestListSize - size].add(newPos + size)
newFileList[--revPos] = newPos to size
} else {
newFileList[--revPos] = file
// stop if not even a size 1 file could be fit
if (size == 1) break
}
}
while (revPos >= 0) {
newFileList[revPos] = fileList[revPos]
revPos--
}
// Gauß to the rescue!
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:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day09_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 1928L)
check(testInputPart2Result == 2858L)
val input = readInput("aoc2024/Day09")
part1(input).println()
part2(input).println()
}
+64
View File
@@ -0,0 +1,64 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
/*
--- Day 10: Hoof It ---
https://adventofcode.com/2024/day/10
*/
fun main() {
val inlineTestInput = """
89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732
"""
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
val starts = grid.findMatchesRelPos { it == '0' }
var sum = 0
for (startPos in starts) {
var nodes = listOf(startPos)
for (c in '1'..'9') {
nodes = nodes.flatMap { grid.marchMatching(it, CharGrid.PLUS_POS) { char -> char == c } }.distinct()
if (nodes.isEmpty()) break
}
sum += nodes.size
}
return sum
}
fun walkTrail(grid: CharGrid, c: Char, p: RelPos): Int {
val newNodes = grid.marchMatching(p, CharGrid.PLUS_POS) { it == c }
return if (c == '9') newNodes.size else newNodes.sumOf { walkTrail(grid, c + 1, it) }
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
return grid.findMatchesRelPos { it == '0' }.sumOf { walkTrail(grid, '1', it) }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day10_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 36)
check(testInputPart2Result == 81)
val input = readInput("aoc2024/Day10")
part1(input).println()
part2(input).println()
}
+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 fuel.Fuel
import fuel.method
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import java.time.LocalDate
import java.time.LocalTime
import java.time.Month
import java.time.ZoneId
import java.util.*
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
fun main() {
var cookie = "<insert your session cookie here or store in gradle.properties>"
@@ -23,7 +27,7 @@ fun main() {
cookie = props.getProperty("cookie", cookie)
}
val downloader = Downloader(2024, "aoc2024", cookie)
val downloader = Downloader(2025, "aoc2025", cookie)
downloader.updateToLatest()
}
@@ -42,15 +46,16 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir)
}
val now = LocalDate.now(ZoneId.of("UTC-1"))
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, 25))) {
val now = LocalDate.now(ZoneId.of("UTC+1"))
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))) {
now.dayOfMonth
} else {
throw IllegalStateException("You're too early.")
}
} else {
25
maxPuzzles
}
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 genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
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")
val (code, data) = downloadInput(day)
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",
method = "GET",
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"
)
)