Compare commits
56
Commits
abb0225d06
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cda0dccc65 | ||
|
|
5e031c27cc | ||
|
|
de3b0795e3 | ||
|
|
2190f9e1ac | ||
|
|
0192b33570 | ||
|
|
a69be406b1 | ||
|
|
7495f9e378 | ||
|
|
ccc3fdad4c | ||
|
|
bd591263f6 | ||
|
|
4982f2cb3c | ||
|
|
6299b10b85 | ||
|
|
caf82dd7a9 | ||
|
|
d2044f2d6a | ||
|
|
9a2a22805b | ||
|
|
909fb5c7d2 | ||
|
|
52af74ecd5 | ||
|
|
7a58627132 | ||
|
|
5fc0e8756a | ||
|
|
2b3549c84c | ||
|
|
128ffbb10f | ||
|
|
f51f87736b | ||
|
|
c82509468a | ||
|
|
732c69df0e | ||
|
|
1f323bcc82 | ||
|
|
17c6a9baac | ||
|
|
ab5fd24260 | ||
|
|
f399fcdea7 | ||
|
|
9a0b4af4ac | ||
|
|
79360efdf4 | ||
|
|
d49d3614c7 | ||
|
|
1a303d9632 | ||
|
|
e7d733846f | ||
|
|
e00e002cd6 | ||
|
|
0d5809398c | ||
|
|
277bc6cc8e | ||
|
|
e222975b2a | ||
|
|
343414cd04 | ||
|
|
bc542bd29c | ||
|
|
0cb180ddf3 | ||
|
|
f494b763cf | ||
|
|
aa0f1dad74 | ||
|
|
9047a97185 | ||
|
|
1b2332a49e | ||
|
|
b0e4461a7d | ||
|
|
76c01e01e5 | ||
|
|
3ee70151f1 | ||
|
|
0592f4a862 | ||
|
|
0d6bf1ca43 | ||
|
|
9e7ac0057b | ||
|
|
90cf5a8335 | ||
|
|
9a8b316122 | ||
|
|
778b38b3a4 | ||
|
|
34c0c213c4 | ||
|
|
3d17ed29c7 | ||
|
|
bc1888299b | ||
|
|
4b84ce2803 |
+3
-3
@@ -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
@@ -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
|
||||
|
||||
+17
-3
@@ -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,6 +204,12 @@ 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)) }
|
||||
|
||||
@@ -215,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -2,6 +2,7 @@ package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
--- Day 9: Disk Fragmenter ---
|
||||
@@ -45,60 +46,55 @@ fun main() {
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val diskSize = input[0].sumOf { it - '0' }
|
||||
val bitmap = IntArray(diskSize) { 0 }
|
||||
var pos = 0
|
||||
var isFile = true
|
||||
var fileId = 0
|
||||
val freeList = HashMap<Int, MutableList<Int>>()
|
||||
val fileList = ArrayList<Pair<Int, Pair<Int, Int>>>()
|
||||
val newFileList = ArrayList<Pair<Int, Pair<Int, Int>>>()
|
||||
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) {
|
||||
if (size == 0) println("Narf!")
|
||||
fileList.add(fileId to (pos to size))
|
||||
fileId++
|
||||
} else {
|
||||
if (size > 0) {
|
||||
freeList.getOrPut(size) { ArrayDeque(0) }.add(pos)
|
||||
}
|
||||
fileList.add(pos to size)
|
||||
} else if (size > 0) {
|
||||
freeList[size].add(pos)
|
||||
}
|
||||
pos += size
|
||||
isFile = !isFile
|
||||
}
|
||||
|
||||
for (file in fileList.reversed()) {
|
||||
val (filePos, size) = file.second
|
||||
var bestList: MutableList<Int>? = null
|
||||
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]
|
||||
if (list?.isNotEmpty() == true && list[0] < filePos) {
|
||||
if (bestList == null || list[0] < bestList[0]) {
|
||||
bestList = list
|
||||
val firstPos = list.firstOrNull()
|
||||
if ((firstPos != null && firstPos < filePos) &&
|
||||
(bestListSize == 0 || firstPos < bestListPos)
|
||||
) {
|
||||
bestListSize = s
|
||||
bestListPos = firstPos
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestList != null) {
|
||||
val newPos = bestList.removeAt(0)
|
||||
if (bestListSize > size) {
|
||||
val remBucket = freeList.getOrPut(bestListSize - size) { ArrayDeque(0) }
|
||||
remBucket.add(newPos + size)
|
||||
remBucket.sort()
|
||||
}
|
||||
newFileList.add(file.first to (newPos to size))
|
||||
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.add(file)
|
||||
newFileList[--revPos] = file
|
||||
// stop if not even a size 1 file could be fit
|
||||
if (size == 1) break
|
||||
}
|
||||
}
|
||||
for (file in newFileList) {
|
||||
for (i in 0 until file.second.second) {
|
||||
bitmap[i + file.second.first] = file.first
|
||||
while (revPos >= 0) {
|
||||
newFileList[revPos] = fileList[revPos]
|
||||
revPos--
|
||||
}
|
||||
}
|
||||
return bitmap.map(Int::toLong).reduceIndexed { index, acc, i -> acc + index * i }
|
||||
// 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:
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user