Compare commits

...
26 Commits
Author SHA1 Message Date
chrisly42 cda0dccc65 Day 12. Argh. 2025-12-15 21:29:08 +01:00
chrisly42 5e031c27cc WiP 2025-12-15 09:55:01 +01:00
chrisly42 de3b0795e3 Intermediate commit before I break stuff. 2025-12-11 15:30:13 +01:00
chrisly42 2190f9e1ac No need to have two different recursion functions for Day 11. Tidied up. 2025-12-11 08:00:08 +01:00
chrisly42 0192b33570 Day 10 still not working, but math will save me. 2025-12-11 07:25:11 +01:00
chrisly42 a69be406b1 Day 11. 2025-12-11 07:24:13 +01:00
chrisly42 7495f9e378 Still non-working part 2. But I have an idea. 2025-12-10 21:43:08 +01:00
chrisly42 ccc3fdad4c Restored deleted code from previous commit and used it to solve Part 1 much faster. 2025-12-10 18:44:40 +01:00
chrisly42 bd591263f6 Still not working, but stuff removed that was wrong. 2025-12-10 17:02:28 +01:00
chrisly42 4982f2cb3c Non working solution before I re-read the instructions regarding the light diagrams to be ignored for part 2. 2025-12-10 16:54:58 +01:00
chrisly42 6299b10b85 Day 10. Part 2 doesn't terminate in time and space yet. 2025-12-10 07:44:08 +01:00
Chris Hodges caf82dd7a9 Restored "clever" method for day 9. Not much faster, though.
Change-Id: I8ced811be7415db8281a13f1f8200192b05914e2
2025-12-09 13:06:28 +01:00
chrisly42 d2044f2d6a Day 9. Removed all smart code. 2025-12-09 08:36:22 +01:00
chrisly42 9a2a22805b Day 9. Tried to be smart, that was a failure. 2025-12-09 08:34:40 +01:00
chrisly42 909fb5c7d2 Day 8: Use long for result of part 2. X coords are > 16 bit. That it didn't overflow was pure chance. 2025-12-08 07:07:39 +01:00
chrisly42 52af74ecd5 Day 8: Use priority queue. 2025-12-08 07:04:34 +01:00
chrisly42 7a58627132 Day 8. 2025-12-08 06:58:11 +01:00
chrisly42 5fc0e8756a Day 7 :( 2025-12-07 07:41:53 +01:00
chrisly42 2b3549c84c Day 6. 2025-12-06 06:46:07 +01:00
chrisly42 128ffbb10f English... 2025-12-06 06:46:02 +01:00
chrisly42 f51f87736b Minor tweaking of Day 5. 2025-12-05 06:27:46 +01:00
chrisly42 c82509468a Day 5. 2025-12-05 06:21:02 +01:00
chrisly42 732c69df0e Day 4. 2025-12-04 06:41:19 +01:00
chrisly42 1f323bcc82 Restored missing empty line. 2025-12-03 06:48:13 +01:00
chrisly42 17c6a9baac Day 3. 2025-12-03 06:47:11 +01:00
chrisly42 ab5fd24260 Modified Downloader to wait until the time is right before attempting to download today's puzzle. 2025-12-02 23:01:42 +01:00
14 changed files with 1030 additions and 5 deletions
+101 -1
View File
@@ -1,4 +1,5 @@
import java.lang.Long.numberOfTrailingZeros import java.lang.Long.numberOfTrailingZeros
import java.math.BigInteger
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@@ -187,7 +188,106 @@ fun gcdPositive(aIn: Long, bIn: Long): Long {
return a shl shift return a shl shift
} }
fun calcPrimeFactorsAndPhi(n: Long, primes: MutableList<Long>, allPrimes: MutableSet<Long>): Pair<List<Pair<Long, Int>>, Long> { // ax + by = gcdExtendedPositive(a, b)
fun extendedGcd(a: Long, b: Long): Pair<Long, Pair<Long, Long>> {
var old_r = a
var r = b
var old_s = 1L
var s = 0L
var old_t = 0L
var t = 1L
while (r != 0L) {
val q = old_r / r
val rtmp = old_r
old_r = r
r = rtmp - q * r
val stmp = old_s
old_s = s
s = stmp - q * s
val ttmp = old_t
old_t = t
t = ttmp - q * t
}
return old_r to (old_s to old_t)
}
fun extendedGcd(a: BigInteger, b: BigInteger): Pair<BigInteger, Pair<BigInteger, BigInteger>> {
var old_r = a
var r = b
var old_s = BigInteger.ONE
var s = BigInteger.ZERO
var old_t = BigInteger.ZERO
var t = BigInteger.ONE
while (r != BigInteger.ZERO) {
val q = old_r / r
val rtmp = old_r
old_r = r
r = rtmp - q * r
val stmp = old_s
old_s = s
s = stmp - q * s
val ttmp = old_t
old_t = t
t = ttmp - q * t
}
return old_r to (old_s to old_t)
}
fun extendedGcd(v: List<Long>): Pair<Long, List<Long>> {
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
val gcds = ArrayList<Long>(v.size)
val coeffs = ArrayList<Long>(v.size)
var (gcd, p1) = extendedGcd(v[0], v[1])
coeffs.add(p1.first)
coeffs.add(p1.second)
gcds.add(gcd)
gcds.add(gcd)
for (i in 2 until v.size) {
val (gcdnew, pi) = extendedGcd(gcd, v[i])
gcd = gcdnew
coeffs.add(pi.second)
gcds.add(gcd)
}
for (i in gcds.indices) {
if (gcds[i] != gcd) {
coeffs[i] *= gcds[i] / gcd
}
}
return gcd to coeffs
}
fun extendedGcdBigInteger(v: List<BigInteger>): Pair<BigInteger, List<BigInteger>> {
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
val gcds = ArrayList<BigInteger>(v.size)
val coeffs = ArrayList<BigInteger>(v.size)
var (gcd, p1) = extendedGcd(v[0], v[1])
coeffs.add(p1.first)
coeffs.add(p1.second)
gcds.add(gcd)
gcds.add(gcd)
for (i in 2 until v.size) {
val (gcdnew, pi) = extendedGcd(gcd, v[i])
gcd = gcdnew
coeffs.add(pi.second)
gcds.add(gcd)
}
for (i in gcds.indices) {
if (gcds[i] != gcd) {
coeffs[i] *= gcds[i] / gcd
}
}
return gcd to coeffs
}
fun calcPrimeFactorsAndPhi(
n: Long,
primes: MutableList<Long>,
allPrimes: MutableSet<Long>
): Pair<List<Pair<Long, Int>>, Long> {
val factors = ArrayList<Pair<Long, Int>>() val factors = ArrayList<Pair<Long, Int>>()
var phi = 1L var phi = 1L
var rem = n var rem = n
+4 -1
View File
@@ -152,6 +152,9 @@ class CharGrid {
fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) = fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) =
generateGridPos().forEach { this[it] = op(this, it) } generateGridPos().forEach { this[it] = op(this, it) }
fun applyWithPos(relposes: Iterable<RelPos>, op: (grid: CharGrid, pos: RelPos) -> Char?) =
relposes.forEach { this[it] = op(this, it) }
fun apply(op: (content: Char) -> Char?) = fun apply(op: (content: Char) -> Char?) =
applyWithPos { grid: CharGrid, pos -> op(grid[pos]) } applyWithPos { grid: CharGrid, pos -> op(grid[pos]) }
@@ -201,7 +204,7 @@ class CharGrid {
fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.filter { predicate(get(c + it.dc, r + it.dr)) } relposes.filter { predicate(get(c + it.dc, r + it.dr)) }
fun matchRelativeRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = fun matchRelative(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.filter { predicate(get(pos.translate(it))) } relposes.filter { predicate(get(pos.translate(it))) }
fun matchAbsoluteRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = fun matchAbsoluteRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
+1 -1
View File
@@ -82,7 +82,7 @@ Prize: X=18641, Y=10279
// Linear Diophantine equations // Linear Diophantine equations
/* The simplest linear Diophantine equation takes the form /* The simplest linear Diophantine equation takes the form
a*x + b*x = c a*x + b*y = c
where a, b and c are given integers. where a, b and c are given integers.
The solutions are described by the following theorem: The solutions are described by the following theorem:
This Diophantine equation has a solution (where x and y are integers), This Diophantine equation has a solution (where x and y are integers),
+57
View File
@@ -0,0 +1,57 @@
package aoc2025
import println
import readInput
/*
--- Day 3: Lobby ---
https://adventofcode.com/2025/day/3
*/
fun main() {
val inlineTestInput = """
987654321111111
811111111111119
234234234234278
818181911112111
"""
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
fun rec(i: String, si: Int, left: Int): Long {
for (dig in 9 downTo 1) {
val dp = i.indexOf('0' + dig, si)
if (dp >= 0) {
if (left == 0) {
return dig.toLong()
} else {
val lastDigits = rec(i, dp + 1, left - 1)
if (lastDigits >= 0) return lastDigits + dig * powTenTable[left]
}
}
}
return -1L
}
fun part1(input: List<String>): Long {
return input.sumOf { rec(it, 0, 1) }
}
fun part2(input: List<String>): Long {
return input.sumOf { rec(it, 0, 11) }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day03_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 357L)
check(testInputPart2Result == 3121910778619L)
val input = readInput("aoc2025/Day03")
part1(input).println()
part2(input).println()
}
+56
View File
@@ -0,0 +1,56 @@
package aoc2025
import CharGrid
import println
import readInput
/*
--- Day 4: Printing Department ---
https://adventofcode.com/2025/day/4
*/
fun main() {
val inlineTestInput = """
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
"""
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
return matches.count()
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
var removed = 0
do {
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
grid.applyWithPos(matches) { _, _ -> 'x' }
removed += matches.size
} while (matches.isNotEmpty())
return removed
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day04_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 13)
check(testInputPart2Result == 43)
val input = readInput("aoc2025/Day04")
part1(input).println()
part2(input).println()
}
+79
View File
@@ -0,0 +1,79 @@
package aoc2025
import println
import readInput
/*
--- Day 5: Cafeteria ---
https://adventofcode.com/2025/day/5
*/
fun main() {
val inlineTestInput = """
3-5
10-14
16-20
12-18
1
5
8
11
17
32
"""
fun part1(input: List<String>): Int {
val ranges = ArrayList<LongRange>()
var freshNum = 0
for (i in input) {
if (i.contains("-")) {
val (low, high) = i.split("-").map { it.toLong() }
ranges.add(LongRange(low, high))
} else if (i.isNotBlank()) {
if (ranges.any { i.toLong() in it }) freshNum++
}
}
return freshNum
}
fun part2(input: List<String>): Long {
val ranges = ArrayList<LongRange>()
for (i in input) {
if (i.contains("-")) {
val (low, high) = i.split("-").map { it.toLong() }
ranges.add(LongRange(low, high))
}
}
val consolidated = ArrayList<LongRange>()
ranges.sortBy { it.first }
var lastLow = ranges[0].first
var lastHigh = ranges[0].last
for (r in 1..ranges.lastIndex) {
if (ranges[r].first > lastHigh) {
consolidated.add(LongRange(lastLow, lastHigh))
lastLow = ranges[r].first
lastHigh = ranges[r].last
} else {
lastHigh = lastHigh.coerceAtLeast(ranges[r].last)
}
}
consolidated.add(LongRange(lastLow, lastHigh))
return consolidated.sumOf { it.last - it.first + 1 }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day05_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 3)
check(testInputPart2Result == 14L)
val input = readInput("aoc2025/Day05")
part1(input).println()
part2(input).println()
}
+56
View File
@@ -0,0 +1,56 @@
package aoc2025
import println
import readInput
import splitLongs
/*
--- Day 6: Trash Compactor ---
https://adventofcode.com/2025/day/6
*/
fun main() {
val inlineTestInput = """
123 328 51 64
45 64 387 23
6 98 215 314
* + * +
"""
fun part1(input: List<String>): Long {
val numbers = input.dropLast(1).map { it.splitLongs().toLongArray() }.toList()
val ops = input.last().split(" ").filter(String::isNotBlank)
return ops.withIndex().sumOf { (c, op) -> if (op == "+") numbers.sumOf { it[c] } else numbers.fold(1L) { acc, row -> acc * row[c] } }
}
fun part2(input: List<String>): Long {
val numbers = ArrayList<LongArray>()
val maxLineLength = input.maxOf { it.length }
val spaceMask = BooleanArray(maxLineLength)
input.forEach { it.withIndex().filter { (_, ch) -> ch != ' ' }.forEach { (c, _) -> spaceMask[c] = true } }
val numColl = ArrayList<Long>()
for (c in 0 until maxLineLength) {
if (spaceMask[c]) numColl.add(input.dropLast(1).map { it[c] }.joinToString("").trim().toLong())
if (!spaceMask[c] || c == maxLineLength - 1) {
numbers.add(numColl.toLongArray())
numColl.clear()
}
}
return input.last().split(" ").filter(String::isNotBlank).withIndex()
.sumOf { (c, op) -> if (op == "+") numbers[c].sum() else numbers[c].fold(1L) { acc, row -> acc * row } }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day06_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 4277556L)
check(testInputPart2Result == 3263827L)
val input = readInput("aoc2025/Day06")
part1(input).println()
part2(input).println()
}
+84
View File
@@ -0,0 +1,84 @@
package aoc2025
import CharGrid
import println
import readInput
/*
--- Day 7: Laboratories ---
https://adventofcode.com/2025/day/7
*/
fun main() {
val inlineTestInput = """
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
"""
val memo = ArrayList<HashMap<Int, Long>>()
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
var splits = 0
for (row in 2 until grid.height step 2) {
for (col in 1 until grid.width - 1) {
val ch = grid[col, row - 2]
if (ch == 'S') {
if (grid[col, row] == '^') {
grid[col - 1, row] = 'S'
grid[col + 1, row] = 'S'
splits++
} else {
grid[col, row] = 'S'
}
}
}
}
return splits
}
fun rec(grid: CharGrid, col: Int, row: Int): Long {
if (row >= grid.height) return 1
return memo[row / 2].getOrPut(col) {
if (grid[col, row + 2] == '^')
rec(grid, col - 1, row + 2) + rec(grid, col + 1, row + 2)
else
rec(grid, col, row + 2)
}
}
fun part2(input: List<String>): Long {
val grid = CharGrid(input)
val col = grid.collectMatches { it == 'S' }.single().second.dc
while (memo.size <= grid.height / 2) memo.add(HashMap())
return rec(grid, col, 0)
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day07_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 21)
check(testInputPart2Result == 40L)
val input = readInput("aoc2025/Day07")
part1(input).println()
part2(input).println()
}
+123
View File
@@ -0,0 +1,123 @@
package aoc2025
import println
import readInput
import splitInts
import java.util.*
import kotlin.math.sqrt
/*
--- Day 8: Playground ---
https://adventofcode.com/2025/day/8
*/
fun main() {
val inlineTestInput = """
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689
"""
fun dist(n1: IntArray, n2: IntArray) =
sqrt(((n1[0] - n2[0]).toFloat() * (n1[0] - n2[0]) + (n1[1] - n2[1]).toFloat() * (n1[1] - n2[1]) + (n1[2] - n2[2]).toFloat() * (n1[2] - n2[2])))
fun find(parent: IntArray, n: Int): Int {
if (parent[n] != n) {
parent[n] = find(parent, parent[n])
}
return parent[n]
}
fun union(parent: IntArray, n1: Int, n2: Int): Boolean {
val r1 = find(parent, n1)
val r2 = find(parent, n2)
if (r1 != r2) {
parent[r1] = r2
return true
}
return false
}
// having an oct-tree might speed up things here, so we skip far distances
fun createDistances(nodes: Array<IntArray>): PriorityQueue<Pair<Pair<Int, Int>, Float>> {
val distances = PriorityQueue<Pair<Pair<Int, Int>, Float>>(nodes.size * nodes.size / 2, Comparator.comparing { it.second })
for (i1 in 0 until nodes.size) {
for (i2 in i1 + 1 until nodes.size) {
val d = dist(nodes[i1], nodes[i2])
distances.add((i1 to i2) to d)
}
}
return distances
}
fun part1(input: List<String>, numConnections: Int): Long {
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
val distances = createDistances(nodes)
val n = nodes.size
var numCircuits = 0
val unionFindParentArray = IntArray(n) { it }
while (distances.isNotEmpty()) {
val (n1, n2) = distances.remove().first
union(unionFindParentArray, n1, n2)
numCircuits++
if (numCircuits == numConnections) break
}
val circuitSizes = LongArray(n)
for (i in 0 until n) {
circuitSizes[find(unionFindParentArray, i)]++
}
return circuitSizes.sortedDescending().take(3).fold(1L) { acc, l -> acc * l }
}
fun part2(input: List<String>): Long {
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
val distances = createDistances(nodes)
val n = nodes.size
var numCircuits = 1
val unionFindParentArray = IntArray(n) { it }
while (distances.isNotEmpty()) {
val (n1, n2) = distances.remove().first
if (union(unionFindParentArray, n1, n2)) {
numCircuits++
if (numCircuits == n) {
return nodes[n1][0].toLong() * nodes[n2][0]
}
}
}
return -1L
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day08_test")
val testInputPart1Result = part1(testInput, 10)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 40L)
check(testInputPart2Result == 25272L)
val input = readInput("aoc2025/Day08")
part1(input, 1000).println()
part2(input).println()
}
+91
View File
@@ -0,0 +1,91 @@
package aoc2025
import println
import readInput
import splitInts
import java.util.*
import kotlin.math.abs
/*
--- Day 9: Movie Theater ---
https://adventofcode.com/2025/day/9
*/
fun main() {
val inlineTestInput = """
7,1
11,1
11,7
9,7
9,5
2,5
2,3
7,3
"""
fun part1(input: List<String>): Long {
val coords = input.map { it.splitInts(",") }
var maxArea = 0L
for (i1 in 0 until coords.size step 2) {
for (i2 in i1 + 2 until coords.size step 2) {
maxArea =
((abs(coords[i1][0] - coords[i2][0]) + 1).toLong() * (abs(coords[i1][1] - coords[i2][1]) + 1).toLong()).coerceAtLeast(
maxArea
)
}
}
return maxArea
}
fun intersectsRanges(set: TreeMap<Int, ArrayList<IntRange>>, x1: Int, y1: Int, x2: Int, y2: Int) =
set.subMap(y1 + 1, y2).values.any { it.any { it.first < x2 && it.last > x1 } }
fun part2(input: List<String>): Long {
val coords = input.map { it.splitInts(",") }
val xSpans = TreeMap<Int, ArrayList<IntRange>>()
val ySpans = TreeMap<Int, ArrayList<IntRange>>()
for (i1 in 0 until coords.size) {
val i2 = if (i1 != coords.size - 1) i1 + 1 else 0
if (coords[i1][1] == coords[i2][1]) {
xSpans.getOrPut(coords[i1][1]) { ArrayList<IntRange>() }.add(
IntRange(coords[i1][0].coerceAtMost(coords[i2][0]), coords[i1][0].coerceAtLeast(coords[i2][0]))
)
} else {
ySpans.getOrPut(coords[i1][0]) { ArrayList<IntRange>() }.add(
IntRange(coords[i1][1].coerceAtMost(coords[i2][1]), coords[i1][1].coerceAtLeast(coords[i2][1]))
)
}
}
var maxArea = 0L
for (i1 in 0 until coords.size step 2) {
for (i2 in i1 + 2 until coords.size step 2) {
val x1 = coords[i1][0].coerceAtMost(coords[i2][0])
val x2 = coords[i1][0].coerceAtLeast(coords[i2][0])
val y1 = coords[i1][1].coerceAtMost(coords[i2][1])
val y2 = coords[i1][1].coerceAtLeast(coords[i2][1])
val area = ((x2 - x1 + 1).toLong() * (y2 - y1 + 1).toLong())
if (area > maxArea &&
!(intersectsRanges(xSpans, x1, y1, x2, y2) || intersectsRanges(ySpans, y1, x1, y2, x2))
) {
maxArea = area
}
}
}
return maxArea
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day09_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 50L)
check(testInputPart2Result == 24L)
val input = readInput("aoc2025/Day09")
part1(input).println()
part2(input).println()
}
+134
View File
@@ -0,0 +1,134 @@
package aoc2025
import chineseRemainder
import extendedGcdBigInteger
import primeFactors
import primeSequence
import println
import readInput
import sieveOfErastosthenes
import splitInts
import java.math.BigInteger
/*
--- Day 10: Factory ---
https://adventofcode.com/2025/day/10
*/
fun main() {
val inlineTestInput = """
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}
"""
data class Machine(val size: Int, val target: Int, val toggles: IntArray, val joltage: IntArray)
fun parse(input: List<String>): ArrayList<Machine> {
val machines = ArrayList<Machine>()
for (i in input) {
val stuff = i.split(" ")
val machSize = stuff[0].length - 2
val target = stuff[0].removeSurrounding("[", "]")
.foldIndexed(0) { i, acc, ch -> acc + if (ch == '#') (1 shl i) else 0 }
val toggles =
stuff.drop(1).dropLast(1)
.map { it.removeSurrounding("(", ")").splitInts(",").fold(0) { acc, v -> acc + (1 shl v) } }
.toIntArray()
val joltages = stuff.last().removeSurrounding("{", "}").splitInts(",").toIntArray()
machines.add(Machine(machSize, target, toggles, joltages))
}
return machines
}
fun part1(input: List<String>): Int {
val machines = parse(input)
var sumButts = 0
for (m in machines) {
// look at which different possible sets of toggles need to be pressed to result in the target number
// pressing an even time will cancel out the effect, so only look what happens if you press once
var minPresses = Int.MAX_VALUE
for (tm in 1 until (1 shl m.toggles.size)) {
val odds = m.toggles.filterIndexed { i, v -> (1 shl i) and tm != 0 }
val result = odds.fold(0) { acc, iv -> acc xor iv }
if (result == m.target) {
minPresses = minPresses.coerceAtMost(odds.size)
}
}
sumButts += minPresses
}
return sumButts
}
fun part2(input: List<String>): Int {
val machines = parse(input)
val sieve = sieveOfErastosthenes(10000)
val primes = primeSequence(sieve).take(500).toList()
var sumButts = 0
for (m in machines) {
println()
val maxJoltage = m.joltage.max()
val numBits = 32 - maxJoltage.countLeadingZeroBits()
val bigTarget = m.joltage.foldIndexed(BigInteger.ZERO) { index, acc, i ->
acc.plus(
BigInteger.valueOf(i.toLong()).shiftLeft(numBits * index)
)
}
val bigToggles = ArrayList<BigInteger>()
for (t in m.toggles) {
var tt = t
var bigToggle = BigInteger.ZERO
var shift = 0
while (tt > 0) {
if (tt and 1 != 0) {
bigToggle += BigInteger.ONE.shiftLeft(shift)
}
shift += numBits
tt = tt shr 1
}
bigToggles.add(bigToggle)
}
// j0 * bigtoggle[0] + j1 * bigtoggle[1] + ... = bigTarget
// is a Linear Diophantine equation that can be solved with the extended Euclidean algorithm
val (gcd, coeffients) = extendedGcdBigInteger(bigToggles)
if (bigTarget % gcd != BigInteger.ZERO) throw IllegalStateException()
val factorMap = HashMap<Long, Long>()
var good = true
val primeFactors = primeFactors(bigTarget.toLong(), sieve)
for (pf in primeFactors) {
val sf = factorMap[pf]
val pr = 1000 % pf
if (sf != null && sf != pr) {
good = false
break
}
factorMap[pf] = pr
}
val reducedPairs = factorMap.map { it.value to it.key }.sortedBy { it.second }
val rx = reducedPairs.chineseRemainder()
val leastFingers = 0
println(leastFingers)
sumButts += leastFingers
}
return sumButts
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day10_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 7)
//check(testInputPart2Result == 33)
val input = readInput("aoc2025/Day10")
part1(input).println()
part2(input).println()
}
+98
View File
@@ -0,0 +1,98 @@
package aoc2025
import println
import readInput
/*
--- Day 11: Reactor ---
https://adventofcode.com/2025/day/11
*/
fun main() {
val inlineTestInput = """
aaa: you hhh
you: bbb ccc
bbb: ddd eee
ccc: ddd eee fff
ddd: ggg
eee: out
fff: out
ggg: out
hhh: ccc fff iii
iii: out
"""
val inlineTestInput2 = """
svr: aaa bbb
aaa: fft
fft: ccc
bbb: tty
tty: ccc
ccc: ddd eee
ddd: hub
hub: fff
eee: dac
dac: fff
fff: ggg hhh
ggg: out
hhh: out
"""
fun parseInput(input: List<String>): HashMap<String, MutableSet<String>> {
val children = HashMap<String, MutableSet<String>>()
for (i in input) {
val (n, chs) = i.split(": ")
val ch = chs.split(" ")
children.getOrPut(n) { HashSet() }.addAll(ch)
}
return children
}
fun rec(
children: HashMap<String, MutableSet<String>>,
p: String,
stop: String,
visited: MutableSet<String> = HashSet(),
memo: HashMap<String, Long> = HashMap()
): Long {
if (p == stop) return 1L
return memo.getOrPut(p) {
visited.add(p)
val res = children[p]?.sumOf { rec(children, it, stop, visited, memo) } ?: 0L
visited.remove(p)
res
}
}
fun part1(input: List<String>): Long {
return rec(parseInput(input), "you", "out")
}
fun part2(input: List<String>): Long {
val children = parseInput(input)
val srvToDac = rec(children, "svr", "dac")
val dacToFft = rec(children, "dac", "fft")
val fftToOut = rec(children, "fft", "out")
val srvToFft = rec(children, "svr", "fft")
val fftToDac = rec(children, "fft", "dac")
val dacToOut = rec(children, "dac", "out")
return srvToDac * dacToFft * fftToOut + srvToFft * fftToDac * dacToOut
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
val testInput2 = inlineTestInput2.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day11_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput2)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 5L)
check(testInputPart2Result == 2L)
val input = readInput("aoc2025/Day11")
part1(input).println()
part2(input).println()
}
+126
View File
@@ -0,0 +1,126 @@
package aoc2025
import CharGrid
import println
import readInput
import splitInts
/*
--- Day 12: Christmas Tree Farm ---
https://adventofcode.com/2025/day/12
*/
fun main() {
val inlineTestInput = """
0:
###
##.
##.
1:
###
##.
.##
2:
.##
###
##.
3:
##.
###
##.
4:
###
#..
###
5:
###
.#.
###
4x4: 0 0 0 0 2 0
12x5: 1 0 1 0 2 2
12x5: 1 0 1 0 3 2
"""
fun fits(grid: LongArray, x: Int, y: Int, present: Long) =
(grid[y] or ((7L and present) shl x) == grid[y]) &&
(grid[y + 1] or ((7L and (present shr 3)) shl x) == grid[y + 1]) &&
(grid[y + 2] or ((7L and (present shr 6)) shl x) == grid[y + 2])
fun part1(input: List<String>): Int {
var lp = 0
val presents = ArrayList<IntArray>()
// nothing of this stuff is needed :-(
while (!input[lp].contains("x")) {
var charGrid = CharGrid(input.subList(lp + 1, lp + 4))
lp += 5
val setRot = HashSet<Int>()
for (r in 0..7) {
val present = charGrid.generateGridPos().foldIndexed(0) { index, acc, pos -> acc + (if (charGrid[pos] == '#') (1 shl index) else 0) }
setRot.add(present)
if (r != 3) {
// rotate
val newGrid = charGrid.copyOf()
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dr, dc] = charGrid[dc, dr] }
charGrid = newGrid
} else {
// flip
val newGrid = charGrid.copyOf()
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dc, dr] = charGrid[dc, dr] }
charGrid = newGrid
}
}
presents.add(setRot.toIntArray())
}
var fitted = 0
val presentSizes = presents.map { it[0].countOneBits() }.toIntArray()
for (p in lp until input.size) {
val (dim, pl) = input[p].split(": ")
val (width, height) = dim.splitInts("x")
val placements = pl.splitInts().toIntArray()
val totalSize = placements.mapIndexed { i, v -> v * presentSizes[i] }.sum()
if (totalSize > width * height) {
continue
}
val totalPresents = placements.sum()
// just assume it will fit if there is enough area
if (totalPresents * 9 <= width * height) {
fitted++
continue
}
println("Oh no!")
// here the hard part would have started, but except for the example input,
// it never gets here
//val grid = LongArray(height) { (1L shl width) - 1L }
}
return fitted
}
fun part2(input: List<String>): Int {
return 0
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2025/Day12_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
//check(testInputPart1Result == 2)
check(testInputPart2Result == 0)
val input = readInput("aoc2025/Day12")
part1(input).println()
part2(input).println()
}
+20 -2
View File
@@ -4,15 +4,19 @@ import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser
import fuel.Fuel import fuel.Fuel
import fuel.method import fuel.method
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime
import java.time.Month import java.time.Month
import java.time.ZoneId import java.time.ZoneId
import java.util.* import java.util.*
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
fun main() { fun main() {
var cookie = "<insert your session cookie here or store in gradle.properties>" var cookie = "<insert your session cookie here or store in gradle.properties>"
@@ -42,7 +46,7 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
if (!Files.exists(targetDir)) { if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir) Files.createDirectories(targetDir)
} }
val now = LocalDate.now(ZoneId.of("UTC-1")) val now = LocalDate.now(ZoneId.of("UTC+1"))
val maxPuzzles = if (year < 2025) 25 else 12 val maxPuzzles = if (year < 2025) 25 else 12
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, maxPuzzles))) { val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, maxPuzzles))) {
if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) { if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) {
@@ -60,6 +64,20 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day)) val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day))
val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day)) val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
if (!Files.exists(inputFile)) { if (!Files.exists(inputFile)) {
if (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour < 5) {
println("Puzzle will not be available within the next hour. Skipping.")
break
}
while (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour == 5) {
println("Waiting for puzzle to become available...")
runBlocking {
if (LocalTime.now(ZoneId.of("UTC+1")).minute < 59) {
delay(1.minutes)
} else {
delay(1.seconds)
}
}
}
println("Attempting to download input for day $day") println("Attempting to download input for day $day")
val (code, data) = downloadInput(day) val (code, data) = downloadInput(day)
if (code != 200) { if (code != 200) {
@@ -157,7 +175,7 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
url = "https://adventofcode.com/$year/day/$day$suffix", url = "https://adventofcode.com/$year/day/$day$suffix",
method = "GET", method = "GET",
headers = mapOf( headers = mapOf(
"User-Agent" to "git.platon42.de/chrisly42/advent-of-code", "User-Agent" to "git.platon42.de/chrisly42/advent-of-code (chrisly@platon42.de)",
"Cookie" to "session=$sessionCookie" "Cookie" to "session=$sessionCookie"
) )
) )