Compare commits
37 Commits
f32db4397c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
7a1dba17f1
|
|||
|
0f2b85f2ad
|
|||
|
f16258eae7
|
|||
|
aef2dc6159
|
|||
|
5fd63ac62f
|
|||
|
c359318235
|
|||
|
162b48d5cb
|
|||
|
2a5388e90b
|
|||
|
6f69aae304
|
|||
|
874622fc78
|
|||
|
68eec5b3b3
|
|||
|
a3195fc0d9
|
|||
|
b2ff775638
|
|||
| ccf7dd091c | |||
| dafef2c099 | |||
|
9bbf08f489
|
|||
|
9e3e1adeaa
|
|||
|
8f47a462df
|
|||
|
fcaa401d60
|
|||
|
67ce498228
|
|||
|
83c078a4cb
|
|||
| 0f75670efb | |||
|
c1179a2f11
|
|||
|
f416a0f07c
|
|||
|
beb4b9ef6c
|
|||
|
6a42539f09
|
|||
|
a36d0efd4b
|
|||
|
143679ec7e
|
|||
|
12cab8af56
|
|||
|
29a9f321cd
|
|||
|
92176926c7
|
|||
|
0a78452ca9
|
|||
|
a4264163af
|
|||
|
24f65fd994
|
|||
|
3992e5f484
|
|||
|
07e25fadd1
|
|||
|
80ffa962c0
|
40
.github/workflows/solve.yaml
vendored
40
.github/workflows/solve.yaml
vendored
@@ -8,19 +8,53 @@ on:
|
||||
jobs:
|
||||
example-action:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DAY01INPUT: ${{ secrets.DAY01INPUT }}
|
||||
DAY02INPUT: ${{ secrets.DAY02INPUT }}
|
||||
DAY03INPUT: ${{ secrets.DAY03INPUT }}
|
||||
DAY04INPUT: ${{ secrets.DAY04INPUT }}
|
||||
DAY05INPUT: ${{ secrets.DAY05INPUT }}
|
||||
DAY06INPUT: ${{ secrets.DAY06INPUT }}
|
||||
DAY07INPUT: ${{ secrets.DAY07INPUT }}
|
||||
DAY08INPUT: ${{ secrets.DAY08INPUT }}
|
||||
DAY09INPUT: ${{ secrets.DAY09INPUT }}
|
||||
DAY10INPUT: ${{ secrets.DAY10INPUT }}
|
||||
DAY11INPUT: ${{ secrets.DAY11INPUT }}
|
||||
DAY12INPUT: ${{ secrets.DAY12INPUT }}
|
||||
DAY13INPUT: ${{ secrets.DAY13INPUT }}
|
||||
DAY14INPUT: ${{ secrets.DAY14INPUT }}
|
||||
DAY15INPUT: ${{ secrets.DAY15INPUT }}
|
||||
DAY16INPUT: ${{ secrets.DAY16INPUT }}
|
||||
DAY17INPUT: ${{ secrets.DAY17INPUT }}
|
||||
DAY18INPUT: ${{ secrets.DAY18INPUT }}
|
||||
DAY19INPUT: ${{ secrets.DAY19INPUT }}
|
||||
DAY20INPUT: ${{ secrets.DAY20INPUT }}
|
||||
DAY21INPUT: ${{ secrets.DAY21INPUT }}
|
||||
DAY22INPUT: ${{ secrets.DAY22INPUT }}
|
||||
DAY23INPUT: ${{ secrets.DAY23INPUT }}
|
||||
DAY24INPUT: ${{ secrets.DAY24INPUT }}
|
||||
DAY25INPUT: ${{ secrets.DAY25INPUT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'corretto' # See 'Supported distributions' for available options
|
||||
distribution: 'corretto'
|
||||
java-version: '21'
|
||||
- uses: https://github.com/fwilhe2/setup-kotlin@main
|
||||
- name: Run tests
|
||||
run: ./simpletest.sh validate
|
||||
- name: Solutions
|
||||
run: |
|
||||
set -e
|
||||
for script in $(ls *.kts | sort -r); do
|
||||
echo "Running $script" | tee -a solutions-output.txt
|
||||
kotlin $script 2>&1 | tee -a solutions-output.txt
|
||||
dayname=$(basename $script .kts | tr 'a-z' 'A-Z')INPUT
|
||||
input_var="${!dayname}"
|
||||
if [ -z "$input_var" ]; then
|
||||
echo "Skipping $script: No input found for $dayname" | tee -a solutions-output.txt
|
||||
continue
|
||||
fi
|
||||
echo "Running $script with input from $dayname" | tee -a solutions-output.txt
|
||||
echo -n "$input_var" | kotlin $script 2>&1 | tee -a solutions-output.txt
|
||||
done
|
||||
- name: Upload Solutions Output
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea
|
||||
*input.txt
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
To run the script, use the following command:
|
||||
|
||||
cat inputfile.txt | kotlin dayxx.kts
|
||||
|
||||
Replace `inputfile.txt` with the name of your input file. You can obtain your personalized input for the problem at [adventofcode.com](https://adventofcode.com). Note that the test values are based on my own inputs and may not match yours.
|
||||
4
day01.kts
Normal file → Executable file
4
day01.kts
Normal file → Executable file
@@ -1,8 +1,8 @@
|
||||
import java.io.File
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
import kotlin.math.abs
|
||||
|
||||
val scanner = Scanner(File("day01input.txt"))
|
||||
val scanner = Scanner(System.`in`)
|
||||
val firstList = mutableListOf<Int>()
|
||||
val secondList = mutableListOf<Int>()
|
||||
var secondMap = mutableMapOf<Int, Int>()
|
||||
|
||||
2
day01.kts.testvalue
Normal file
2
day01.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Total distance: 1603498
|
||||
Similarity score: 25574739
|
||||
1000
day01input.txt
1000
day01input.txt
File diff suppressed because it is too large
Load Diff
4
day02.kts
Normal file → Executable file
4
day02.kts
Normal file → Executable file
@@ -1,8 +1,8 @@
|
||||
import java.io.File
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
import kotlin.math.abs
|
||||
|
||||
val scanner = Scanner(File("day02input.txt"))
|
||||
val scanner = Scanner(System.`in`)
|
||||
var safelines = 0
|
||||
var dampenedsafelines = 0
|
||||
while (scanner.hasNextLine()) {
|
||||
|
||||
2
day02.kts.testvalue
Normal file
2
day02.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Safe lines: 472
|
||||
Safe lines with dampener: 520
|
||||
1000
day02input.txt
1000
day02input.txt
File diff suppressed because it is too large
Load Diff
30
day03.kts
Executable file
30
day03.kts
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
var partOneSum = 0
|
||||
var partTwoSum = 0
|
||||
var doState = true
|
||||
|
||||
while (scanner.hasNextLine()) {
|
||||
val line = scanner.nextLine()
|
||||
val regex = Regex("""(mul\((\d+),(\d+)\))|(do\(\))|(don't\(\))""")
|
||||
val match = regex.findAll(line)
|
||||
for (matchResult in match) {
|
||||
if (matchResult.value == "don't()") {
|
||||
doState = false
|
||||
} else if (matchResult.value == "do()") {
|
||||
doState = true
|
||||
} else {
|
||||
val (a, b, c) = matchResult.destructured
|
||||
val factorOne = b.toInt()
|
||||
val factorTwo = c.toInt()
|
||||
partOneSum += factorOne * factorTwo
|
||||
if (doState) {
|
||||
partTwoSum += factorOne * factorTwo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println("Part One Sum: $partOneSum")
|
||||
println("Part Two Sum: $partTwoSum")
|
||||
2
day03.kts.testvalue
Normal file
2
day03.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Part One Sum: 187833789
|
||||
Part Two Sum: 94455185
|
||||
81
day04.kts
Executable file
81
day04.kts
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
val xMasSearchWord = listOf("M", "A", "S") // Skip X because that triggers the search at 0,0 offset.
|
||||
val xMasSearchOffsets = listOf(
|
||||
listOf(Pair(0, 1), Pair(0, 2), Pair(0, 3)),
|
||||
listOf(Pair(1, 0), Pair(2, 0), Pair(3, 0)),
|
||||
listOf(Pair(0, -1), Pair(0, -2), Pair(0, -3)),
|
||||
listOf(Pair(-1, 0), Pair(-2, 0), Pair(-3, 0)),
|
||||
listOf(Pair(1, 1), Pair(2, 2), Pair(3, 3)),
|
||||
listOf(Pair(-1, 1), Pair(-2, 2), Pair(-3, 3)),
|
||||
listOf(Pair(-1, -1), Pair(-2, -2), Pair(-3, -3)),
|
||||
listOf(Pair(1, -1), Pair(2, -2), Pair(3, -3))
|
||||
)
|
||||
var ycoordinates = mutableListOf<List<String>>()
|
||||
while (scanner.hasNextLine()) {
|
||||
val line = scanner.nextLine()
|
||||
val xcoordinates = line.split("").filter { it.isNotEmpty() }
|
||||
ycoordinates.add(xcoordinates)
|
||||
}
|
||||
var xMasCount = 0
|
||||
var masXCount = 0
|
||||
for (y in ycoordinates.indices) {
|
||||
for (x in ycoordinates[y].indices) {
|
||||
if (ycoordinates[y][x] == "X") {
|
||||
xMasCount += findXmas(y, x)
|
||||
}
|
||||
if (ycoordinates[y][x] == "A") {
|
||||
masXCount += findMasX(y, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun findXmas(y: Int, x: Int): Int {
|
||||
var xmasfound = 0
|
||||
for (searchOffset in xMasSearchOffsets) {
|
||||
val last = searchOffset.last()
|
||||
if ((y+last.first) !in ycoordinates.indices || (x+last.second) !in ycoordinates[y].indices){
|
||||
continue
|
||||
}
|
||||
var match = true
|
||||
for (i in searchOffset.indices) {
|
||||
val (dy, dx) = searchOffset[i]
|
||||
val search = xMasSearchWord[i]
|
||||
if (ycoordinates[y+dy][x+dx] != search) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
xmasfound += 1
|
||||
}
|
||||
}
|
||||
return xmasfound
|
||||
}
|
||||
|
||||
println("Xmas count: $xMasCount")
|
||||
println("Mas X count: $masXCount")
|
||||
|
||||
fun findMasX(y: Int, x: Int): Int {
|
||||
if (y == 0 || x == 0 || y == ycoordinates.lastIndex || x == ycoordinates[y].lastIndex) {
|
||||
return 0
|
||||
}
|
||||
val validChars = mutableListOf("M", "S")
|
||||
if (ycoordinates[y - 1][x - 1] !in validChars) {
|
||||
return 0
|
||||
}
|
||||
val remainsOne = validChars[if(validChars.indexOf(ycoordinates[y - 1][x - 1]) == 0) 1 else 0]
|
||||
if (ycoordinates[y + 1][x + 1] != remainsOne) {
|
||||
return 0
|
||||
}
|
||||
if (ycoordinates[y - 1][x + 1] !in validChars) {
|
||||
return 0
|
||||
}
|
||||
val remainsTwo = validChars[if(validChars.indexOf(ycoordinates[y - 1][x + 1]) == 0) 1 else 0]
|
||||
if (ycoordinates[y + 1][x - 1] != remainsTwo) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
2
day04.kts.testvalue
Normal file
2
day04.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Xmas count: 2573
|
||||
Mas X count: 1850
|
||||
49
day05.kts
Executable file
49
day05.kts
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
var orderingRules = mutableListOf<Pair<Int, Int>>()
|
||||
var pagesList = mutableListOf<MutableList<Int>>()
|
||||
val scanner = Scanner(System.`in`)
|
||||
while (scanner.hasNextLine()) {
|
||||
val line = scanner.nextLine()
|
||||
if (line.contains('|')) {
|
||||
val order = line.split("|").map { it.toInt() }
|
||||
orderingRules.add(Pair(order[0], order[1]))
|
||||
} else if (line.contains(',')) {
|
||||
val pages = line.split(",").map { it.toInt() }
|
||||
pagesList.add(pages.toMutableList())
|
||||
}
|
||||
}
|
||||
var orderedMiddleSum = 0
|
||||
var unorderedMiddleSum = 0
|
||||
for (page in pagesList) {
|
||||
if (isOrdered(page)) {
|
||||
orderedMiddleSum += page[page.size / 2]
|
||||
} else {
|
||||
val orderedPage = page.sortedWith { a, b -> compareByRules(a, b) }
|
||||
unorderedMiddleSum += orderedPage[orderedPage.size / 2]
|
||||
}
|
||||
}
|
||||
|
||||
fun compareByRules(a: Int, b: Int): Int {
|
||||
for ((smaller, bigger) in orderingRules) {
|
||||
if (a == smaller && b == bigger) return -1
|
||||
if (a == bigger && b == smaller) return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun isOrdered(page: MutableList<Int>): Boolean {
|
||||
for (i in orderingRules.indices) {
|
||||
val (smaller, bigger) = orderingRules[i]
|
||||
val smallerIndex = page.indexOf(smaller)
|
||||
val biggerIndex = page.indexOf(bigger)
|
||||
if (smallerIndex != -1 && biggerIndex != -1 && biggerIndex < smallerIndex) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
println("Ordered middle sum: $orderedMiddleSum")
|
||||
println("Unordered middle sum: $unorderedMiddleSum")
|
||||
2
day05.kts.testvalue
Normal file
2
day05.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Ordered middle sum: 5713
|
||||
Unordered middle sum: 5180
|
||||
142
day06.kts
Executable file
142
day06.kts
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.lang.RuntimeException
|
||||
import java.util.Scanner
|
||||
|
||||
data class LocationState(val location: Pair<Int, Int>, val direction: Direction)
|
||||
enum class Direction(val offset: Pair<Int, Int>) {
|
||||
NORTH(Pair(0, -1)),
|
||||
EAST(Pair(1, 0)),
|
||||
SOUTH(Pair(0, 1)),
|
||||
WEST(Pair(-1, 0));
|
||||
|
||||
fun move(currentLocation: Pair<Int, Int>): Pair<Int, Int> {
|
||||
return Pair(
|
||||
currentLocation.first + offset.first,
|
||||
currentLocation.second + offset.second
|
||||
)
|
||||
}
|
||||
fun turnLeft(): Direction {
|
||||
return when (this) {
|
||||
NORTH -> WEST
|
||||
WEST -> SOUTH
|
||||
SOUTH -> EAST
|
||||
EAST -> NORTH
|
||||
}
|
||||
}
|
||||
fun turnRight(): Direction {
|
||||
return when (this) {
|
||||
NORTH -> EAST
|
||||
EAST -> SOUTH
|
||||
SOUTH -> WEST
|
||||
WEST -> NORTH
|
||||
}
|
||||
}
|
||||
fun moveByPartOneRules(
|
||||
currentLocation: Pair<Int, Int>,
|
||||
blocks: Set<Pair<Int, Int>>
|
||||
): LocationState {
|
||||
var currentDirection = this
|
||||
do {
|
||||
val potentialMove = currentDirection.move(currentLocation)
|
||||
if (potentialMove !in blocks) {
|
||||
return LocationState(potentialMove, currentDirection)
|
||||
}
|
||||
currentDirection = currentDirection.turnRight()
|
||||
} while (currentDirection != this)
|
||||
throw RuntimeException("Nowhere to go! $currentLocation $this")
|
||||
}
|
||||
}
|
||||
fun isPositionInTheRoom(player: Pair<Int, Int>, xMax: Int, yMax: Int): Boolean {
|
||||
return player.first in 0..xMax && player.second in 0..yMax
|
||||
}
|
||||
|
||||
fun isLoop(
|
||||
playerLocation: Pair<Int, Int>,
|
||||
playerDirection: Direction,
|
||||
history: List<LocationState>,
|
||||
blocks: MutableSet<Pair<Int, Int>>,
|
||||
potentialBlockLocation: Pair<Int, Int>,
|
||||
xMax: Int,
|
||||
yMax: Int
|
||||
): Boolean {
|
||||
var localLocation = playerLocation
|
||||
var localDirection = playerDirection
|
||||
val localHistory = history.toMutableList()
|
||||
val localBlocks = blocks.toMutableSet()
|
||||
localBlocks.add(potentialBlockLocation)
|
||||
while (LocationState(localLocation, localDirection) !in localHistory) {
|
||||
localHistory.add(LocationState(localLocation, localDirection))
|
||||
val (newLocation, newDirection) = localDirection.moveByPartOneRules(localLocation, localBlocks)
|
||||
localLocation = newLocation
|
||||
localDirection = newDirection
|
||||
if (!isPositionInTheRoom(localLocation, xMax, yMax)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
val visited = mutableSetOf<Pair<Int, Int>>()
|
||||
val history = mutableListOf<LocationState>()
|
||||
var playerLocation = Pair(0, 0)
|
||||
var playerDirection: Direction = Direction.NORTH
|
||||
val blocks = mutableSetOf<Pair<Int, Int>>()
|
||||
val potentialBlocks = mutableSetOf<Pair<Int, Int>>()
|
||||
var yMax = 0
|
||||
var xMax = 0
|
||||
while (scanner.hasNextLine()) {
|
||||
val line = scanner.nextLine()
|
||||
for (i in line.indices) {
|
||||
if (line[i] == '.') {
|
||||
continue
|
||||
}
|
||||
if (line[i] == '>') {
|
||||
playerDirection = Direction.EAST
|
||||
playerLocation = Pair(i, yMax)
|
||||
} else if (line[i] == '<') {
|
||||
playerDirection = Direction.WEST
|
||||
playerLocation = Pair(i, yMax)
|
||||
} else if (line[i] == '^') {
|
||||
playerDirection = Direction.NORTH
|
||||
playerLocation = Pair(i, yMax)
|
||||
} else if (line[i] == 'v') {
|
||||
playerDirection = Direction.SOUTH
|
||||
playerLocation = Pair(i, yMax)
|
||||
} else if (line[i] == '#') {
|
||||
blocks.add(Pair(i, yMax))
|
||||
}
|
||||
}
|
||||
xMax = xMax.coerceAtLeast(line.lastIndex)
|
||||
yMax++
|
||||
}
|
||||
yMax-- // Should represent the max index.
|
||||
val initialPosition = playerLocation.copy()
|
||||
while (isPositionInTheRoom(playerLocation, xMax, yMax)) {
|
||||
visited.add(playerLocation)
|
||||
if (checkForLoopIfObstacleInFront(playerLocation, playerDirection, history)) {
|
||||
potentialBlocks.add(playerDirection.move(playerLocation))
|
||||
}
|
||||
history.add(LocationState(playerLocation, playerDirection))
|
||||
val (newLocation, newDirection) = playerDirection.moveByPartOneRules(playerLocation, blocks)
|
||||
playerLocation = newLocation
|
||||
playerDirection = newDirection
|
||||
}
|
||||
|
||||
println("Player location: ${visited.size}")
|
||||
println("Potential blocks: ${potentialBlocks.size}")
|
||||
|
||||
fun checkForLoopIfObstacleInFront(
|
||||
playerLocation: Pair<Int, Int>,
|
||||
playerDirection: Direction,
|
||||
history: MutableList<LocationState>
|
||||
): Boolean {
|
||||
val potentialBlockLocation = playerDirection.moveByPartOneRules(playerLocation, blocks).location
|
||||
val alreadyVisited = potentialBlockLocation in visited
|
||||
val positionNotInTheRoom = !isPositionInTheRoom(potentialBlockLocation, xMax, yMax)
|
||||
if (alreadyVisited || positionNotInTheRoom) {
|
||||
return false
|
||||
}
|
||||
return isLoop(playerLocation, playerDirection, history, blocks, potentialBlockLocation, xMax, yMax)
|
||||
}
|
||||
2
day06.kts.testvalue
Normal file
2
day06.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Player location: 4883
|
||||
Potential blocks: 1655
|
||||
45
day07.kts
Executable file
45
day07.kts
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
|
||||
var total = 0L
|
||||
var totalTwo = 0L
|
||||
val operations = listOf("*", "+")
|
||||
while (scanner.hasNext()) {
|
||||
val line = scanner.nextLine()
|
||||
val (targetString, rest) = line.split(":", limit = 2).map { it.trim() }
|
||||
val target = targetString.toLong()
|
||||
val values = rest.split(Regex("""\s+""")).map { it.toLong() }
|
||||
if (hitsTarget(target, values, operations)) {
|
||||
total += target
|
||||
totalTwo += target
|
||||
} else if (hitsTarget(target, values, listOf("||") + operations)) {
|
||||
totalTwo += target
|
||||
}
|
||||
}
|
||||
println("Total result: $total")
|
||||
println("Total result (part 2): $totalTwo")
|
||||
|
||||
fun hitsTarget(target: Long, values: List<Long>, operations: List<String>): Boolean {
|
||||
if (values.size == 1) {
|
||||
return target == values[0]
|
||||
}
|
||||
val (operandOne, operandTwo) = values.take(2)
|
||||
if (operandOne > target) {
|
||||
return false
|
||||
}
|
||||
val rest = values.drop(2)
|
||||
for (op in operations) {
|
||||
val result = when (op) {
|
||||
"+" -> operandOne + operandTwo
|
||||
"*" -> operandOne * operandTwo
|
||||
"||" -> "$operandOne$operandTwo".toLong()
|
||||
else -> throw IllegalArgumentException("Unknown operation: $op")
|
||||
}
|
||||
if (hitsTarget(target, listOf(result) + rest, operations)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
2
day07.kts.testvalue
Normal file
2
day07.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Total result: 12553187650171
|
||||
Total result (part 2): 96779702119491
|
||||
82
day08.kts
Executable file
82
day08.kts
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
|
||||
val antennaMap = mutableMapOf<String, MutableList<Pair<Int, Int>>>()
|
||||
val antinodeSet = mutableSetOf<Pair<Int, Int>>()
|
||||
val harmonicAntinodeSet = mutableSetOf<Pair<Int, Int>>()
|
||||
var xMax = 0
|
||||
var yMax = 0
|
||||
while (scanner.hasNext()) {
|
||||
val symbols = scanner.nextLine().trim().split("").filter { it.isNotEmpty() }
|
||||
xMax = xMax.coerceAtLeast(symbols.lastIndex)
|
||||
for ((i, symbol) in symbols.withIndex()) {
|
||||
if (symbol == ".") continue
|
||||
antennaMap.getOrPut(symbol) { mutableListOf() }.add(Pair(i, yMax))
|
||||
}
|
||||
yMax++
|
||||
}
|
||||
yMax--
|
||||
|
||||
fun getAntinodes(pair: Pair<Int, Int>, pair1: Pair<Int, Int>, xMax: Int, yMax: Int): Collection<Pair<Int, Int>> {
|
||||
val antinodes = mutableSetOf<Pair<Int, Int>>()
|
||||
val diffOne = Pair(pair.first - pair1.first, pair.second - pair1.second)
|
||||
antinodes.add(Pair(pair.first + diffOne.first, pair.second + diffOne.second))
|
||||
antinodes.add(Pair(pair1.first - diffOne.first, pair1.second - diffOne.second))
|
||||
antinodes.removeIf { it.first < 0 || it.second < 0 || it.first > xMax || it.second > yMax }
|
||||
return antinodes
|
||||
}
|
||||
|
||||
fun getHarmonicAntiNodes(
|
||||
pair: Pair<Int, Int>,
|
||||
pair1: Pair<Int, Int>,
|
||||
xMax: Int,
|
||||
yMax: Int
|
||||
): Collection<Pair<Int, Int>> {
|
||||
val antinodes = mutableSetOf<Pair<Int, Int>>()
|
||||
val diffOne = Pair(pair.first - pair1.first, pair.second - pair1.second)
|
||||
var mult = 0
|
||||
while ((pair.first + mult * diffOne.first) <= xMax && (pair.second + mult * diffOne.second) <= yMax && 0 <= (pair.first + mult * diffOne.first) && 0 <= (pair.second + mult * diffOne.second) ) {
|
||||
antinodes.add(Pair(pair.first + mult * diffOne.first, pair.second + mult * diffOne.second))
|
||||
mult++
|
||||
}
|
||||
mult = 0
|
||||
while ((pair1.first - mult * diffOne.first) <= xMax && (pair1.second - mult * diffOne.second) <= yMax && 0 <= (pair1.first - mult * diffOne.first) && 0 <= (pair1.second - mult * diffOne.second)) {
|
||||
antinodes.add(Pair(pair1.first - mult * diffOne.first, pair1.second - mult * diffOne.second))
|
||||
mult++
|
||||
}
|
||||
antinodes.removeIf { it.first < 0 || it.second < 0 || it.first > xMax || it.second > yMax }
|
||||
return antinodes
|
||||
}
|
||||
|
||||
|
||||
for (antennaType in antennaMap.keys) {
|
||||
for (i in antennaMap[antennaType]!!.indices) {
|
||||
for (y in (i + 1)..(antennaMap[antennaType]!!.lastIndex)) {
|
||||
val antinodes = getAntinodes(antennaMap[antennaType]!![i], antennaMap[antennaType]!![y], xMax, yMax)
|
||||
val harmonicAntinodes = getHarmonicAntiNodes(antennaMap[antennaType]!![i], antennaMap[antennaType]!![y], xMax, yMax)
|
||||
antinodeSet.addAll(antinodes)
|
||||
harmonicAntinodeSet.addAll(harmonicAntinodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun printMap(antinodeSet: Set<Pair<Int, Int>>) {
|
||||
for (y in 0..yMax) {
|
||||
for (x in 0..xMax) {
|
||||
if (Pair(x, y) in antennaMap.values.flatten()) {
|
||||
for (antennatype in antennaMap.keys) {
|
||||
if (Pair(x, y) in antennaMap[antennatype]!!) {
|
||||
print(antennatype)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if(Pair(x, y) in antinodeSet) print("#") else print(".")
|
||||
}
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
println("Antinodes: ${antinodeSet.size}")
|
||||
println("Harmonic Antinodes: ${harmonicAntinodeSet.size}")
|
||||
2
day08.kts.testvalue
Normal file
2
day08.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Antinodes: 280
|
||||
Harmonic Antinodes: 958
|
||||
116
day09.kts
Executable file
116
day09.kts
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.lang.RuntimeException
|
||||
import java.util.Scanner
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
if (!scanner.hasNext()) {
|
||||
throw RuntimeException("No input given.")
|
||||
}
|
||||
val fileSystem = mutableListOf<Int>()
|
||||
val fileSystemDesc = scanner.nextLine().split("").filter { it.isNotEmpty() }.map { it.toInt() }.toMutableList()
|
||||
for (i in 0..fileSystemDesc.lastIndex) {
|
||||
for (j in 0 until fileSystemDesc[i]) {
|
||||
if ((i and 1) == 0) {
|
||||
fileSystem.add(i / 2)
|
||||
} else {
|
||||
fileSystem.add(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
val compressed = compressPartOne(fileSystem)
|
||||
val checksumOne = calculateChecksum(compressed)
|
||||
|
||||
println("Part one checksum: $checksumOne")
|
||||
|
||||
val compressedTwo = compressPartTwo(fileSystem)
|
||||
val checksumTwo = calculateChecksum(compressedTwo)
|
||||
|
||||
println("Part two checksum: $checksumTwo")
|
||||
|
||||
fun compressPartOne(fileSystem: MutableList<Int>): MutableList<Int> {
|
||||
val compressed = fileSystem.toMutableList()
|
||||
var freeSpacePointer = 0
|
||||
var lastSectorPointer = compressed.lastIndex
|
||||
while (freeSpacePointer <= lastSectorPointer) {
|
||||
if (compressed[freeSpacePointer] != -1) {
|
||||
freeSpacePointer++
|
||||
continue
|
||||
}
|
||||
if (compressed[lastSectorPointer] == -1) {
|
||||
lastSectorPointer--
|
||||
continue
|
||||
}
|
||||
val tmp = compressed[freeSpacePointer]
|
||||
compressed[freeSpacePointer] = compressed[lastSectorPointer]
|
||||
compressed[lastSectorPointer] = tmp
|
||||
lastSectorPointer--
|
||||
freeSpacePointer++
|
||||
}
|
||||
return compressed
|
||||
}
|
||||
|
||||
fun calculateChecksum(compressed: MutableList<Int>): Long {
|
||||
var checksum = 0L
|
||||
for (i in 0..compressed.lastIndex) {
|
||||
if (compressed[i] != -1) {
|
||||
checksum += i.toLong() * compressed[i].toLong()
|
||||
}
|
||||
}
|
||||
return checksum
|
||||
}
|
||||
|
||||
fun compressPartTwo(fileSystem: MutableList<Int>): MutableList<Int> {
|
||||
val compressed = fileSystem.toMutableList()
|
||||
var relevantFileId = compressed[compressed.lastIndex]
|
||||
|
||||
for (i in compressed.lastIndex downTo 0) {
|
||||
if (compressed[i] == relevantFileId && relevantFileId >= 0) {
|
||||
val fileSize = findFileSize(compressed, i, relevantFileId)
|
||||
val startIndex = findStartIndexOfEmptySpaceOfLength(compressed, fileSize)
|
||||
if (startIndex != -1 && startIndex < i) {
|
||||
for (j in 0 until fileSize) {
|
||||
compressed[startIndex + j] = compressed[i - j]
|
||||
compressed[i - j] = -1
|
||||
}
|
||||
}
|
||||
relevantFileId--
|
||||
}
|
||||
}
|
||||
return compressed
|
||||
}
|
||||
|
||||
fun findFileSize(compressed: MutableList<Int>, i: Int, relevantFileId: Int): Int {
|
||||
var fileSize = 0
|
||||
var j = i
|
||||
while (j >= 0 && compressed[j] == relevantFileId) {
|
||||
fileSize++
|
||||
j--
|
||||
}
|
||||
return fileSize
|
||||
}
|
||||
|
||||
fun findStartIndexOfEmptySpaceOfLength(compressed: MutableList<Int>, fileSize: Int): Int {
|
||||
var startIndex = 0
|
||||
while (startIndex < compressed.size) {
|
||||
if (compressed[startIndex] != -1) {
|
||||
startIndex++
|
||||
} else {
|
||||
var emptySpaceLength = 0
|
||||
for (i in startIndex until (startIndex + fileSize)) {
|
||||
if (i >= compressed.size) {
|
||||
return -1
|
||||
}
|
||||
if (compressed[i] != -1) {
|
||||
break
|
||||
} else {
|
||||
emptySpaceLength++
|
||||
}
|
||||
if (emptySpaceLength == fileSize) {
|
||||
return startIndex
|
||||
}
|
||||
}
|
||||
startIndex += emptySpaceLength
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
2
day09.kts.testvalue
Normal file
2
day09.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Part one checksum: 6607511583593
|
||||
Part two checksum: 6636608781232
|
||||
82
day10.kts
Executable file
82
day10.kts
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
|
||||
class TrailHead(private val x: Int, private val y: Int, private val map: Map) {
|
||||
private val peaks = mutableMapOf<Pair<Int, Int>, Int>()
|
||||
fun getScore(): Int {
|
||||
if (this.peaks.isEmpty()) {
|
||||
for (peak in this.walkToPeak(this.x, this.y)) {
|
||||
this.peaks[peak] = (this.peaks[peak] ?: 0) + 1
|
||||
}
|
||||
}
|
||||
return this.peaks.keys.size
|
||||
}
|
||||
fun getGetRating(): Int {
|
||||
this.getScore()
|
||||
return this.peaks.values.sum()
|
||||
}
|
||||
|
||||
private fun walkToPeak(x: Int, y: Int, previous: Int = -1): List<Pair<Int, Int>> {
|
||||
if (x < 0 || x >= map.getLength() || y < 0 || y >= map.getHeight() || map.get(x, y) != (previous + 1)) {
|
||||
return listOf()
|
||||
} else if (map.get(x, y) == 9) {
|
||||
return listOf(Pair(x, y))
|
||||
} else {
|
||||
val current = map.get(x, y)
|
||||
return walkToPeak(x + 1, y, current) + walkToPeak(x - 1, y, current) + walkToPeak(x, y + 1, current) + walkToPeak(x, y - 1, current)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map(private val grid: List<List<Int>>) {
|
||||
private val trailHeads = mutableListOf<TrailHead>()
|
||||
|
||||
init {
|
||||
grid.forEachIndexed { y, row ->
|
||||
row.forEachIndexed { x, height ->
|
||||
if (height == 0) {
|
||||
trailHeads.add(TrailHead(x, y, this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTrailHead(trailHead: TrailHead) {
|
||||
trailHeads.add(trailHead)
|
||||
}
|
||||
|
||||
fun get(x: Int, y: Int): Int {
|
||||
return grid[y][x]
|
||||
}
|
||||
|
||||
fun getHeight(): Int {
|
||||
return grid.size
|
||||
}
|
||||
|
||||
fun getLength(): Int {
|
||||
return if (grid.isNotEmpty()) grid[0].size else 0
|
||||
}
|
||||
|
||||
fun getScore(): Int {
|
||||
return trailHeads.fold(0) { acc, trailHead -> acc + trailHead.getScore() }
|
||||
}
|
||||
|
||||
fun getGetRating(): Int {
|
||||
return trailHeads.fold(0) { acc, trailHead -> acc + trailHead.getGetRating() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
val mapInput = mutableListOf<List<Int>>()
|
||||
while (scanner.hasNext()) {
|
||||
val line = scanner.nextLine()
|
||||
val numbers = line.split("").filter { it.isNotEmpty() }.map { it.toInt() }
|
||||
mapInput.add(numbers)
|
||||
}
|
||||
val map = Map(mapInput.reversed())
|
||||
|
||||
println("Score: ${map.getScore()}")
|
||||
println("Rating: ${map.getGetRating()}")
|
||||
2
day10.kts.testvalue
Normal file
2
day10.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
Score: 754
|
||||
Rating: 1609
|
||||
63
day11.kts
Executable file
63
day11.kts
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
import java.util.PriorityQueue
|
||||
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
|
||||
class PriorityCache<K, V>(private val maxSize: Int) {
|
||||
private val cache = mutableMapOf<K, V>()
|
||||
private val priorityQueue = PriorityQueue<Pair<K, Int>>(compareBy { it.second }) // Pair(Key, Priority)
|
||||
|
||||
fun put(key: K, value: V, priority: Int) {
|
||||
if (cache.size >= maxSize) {
|
||||
val leastPriority = priorityQueue.poll() // Remove the lowest-priority item
|
||||
cache.remove(leastPriority.first)
|
||||
}
|
||||
cache[key] = value
|
||||
priorityQueue.add(Pair(key, priority))
|
||||
}
|
||||
|
||||
fun get(key: K): V? {
|
||||
return cache[key]
|
||||
}
|
||||
|
||||
fun isCached(key: K): Boolean {
|
||||
return cache.containsKey(key)
|
||||
}
|
||||
}
|
||||
val priorityCache = PriorityCache<String, Long>(50000)
|
||||
fun getStonesAfter(input: List<Long>,iterations: Int, cache: PriorityCache<String, Long>): Long {
|
||||
var sum = 0L
|
||||
for (i in input) {
|
||||
sum += getStoneNumber(i, iterations, cache)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
fun getStoneNumber(number: Long, iterations: Int, cache: PriorityCache<String, Long>): Long {
|
||||
if (iterations == 0) {
|
||||
return 1L
|
||||
} else if (cache.isCached("$iterations-$number")) {
|
||||
return cache.get("$iterations-$number")!!
|
||||
} else if (number == 0L) {
|
||||
val stonesum = getStoneNumber(1, iterations - 1, cache)
|
||||
cache.put("$iterations-0", stonesum, iterations)
|
||||
return stonesum
|
||||
} else if (number.toString().length % 2 == 0) {
|
||||
val mid = number.toString().length / 2
|
||||
val left = number.toString().substring(0, mid).toLong()
|
||||
val right = number.toString().substring(mid).toLong()
|
||||
val stonesum = getStoneNumber(left, iterations - 1, cache) + getStoneNumber(right, iterations - 1, cache)
|
||||
cache.put("$iterations-$number", stonesum, iterations)
|
||||
return stonesum
|
||||
} else {
|
||||
val newVal = number * 2024
|
||||
val stonesum = getStoneNumber(newVal, iterations - 1, cache)
|
||||
cache.put("$iterations-$number", stonesum, iterations)
|
||||
return stonesum
|
||||
}
|
||||
}
|
||||
val input = scanner.nextLine().split(" ").map { it.toLong() }
|
||||
println("First part with 25 iterations: ${getStonesAfter(input, 25, priorityCache)}")
|
||||
println("Second part with 75 iterations: ${getStonesAfter(input, 75, priorityCache)}")
|
||||
|
||||
2
day11.kts.testvalue
Normal file
2
day11.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
First part with 25 iterations: 222461
|
||||
Second part with 75 iterations: 264350935776416
|
||||
136
day12.kts
Executable file
136
day12.kts
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env kotlin
|
||||
import java.util.Scanner
|
||||
|
||||
|
||||
val scanner = Scanner(System.`in`)
|
||||
|
||||
class Map(private val grid: List<List<String>>) {
|
||||
private val fields = mutableSetOf<Field>()
|
||||
private val map = mutableListOf<List<Plot>>()
|
||||
init {
|
||||
for ((y, line) in grid.withIndex()) {
|
||||
map.add(line.mapIndexed { x, s -> Plot(Pair(x, y), s) })
|
||||
}
|
||||
for (y in 0 until this.getHeight()) {
|
||||
for (x in 0 until this.getWidth()) {
|
||||
val plot = getPlot(x, y)
|
||||
if (!plot.hasField()) {
|
||||
val field = Field(this)
|
||||
plot.addField(field)
|
||||
fields.add(field)
|
||||
field.expand(plot)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
fun getWidth(): Int = grid[0].size
|
||||
fun getHeight(): Int = grid.size
|
||||
fun getPlot(x: Int, y: Int): Plot = map[y][x]
|
||||
|
||||
fun getFields(): Set<Field> = fields
|
||||
|
||||
fun getPrice(): Long = fields.sumOf { it.getPrice() }
|
||||
fun getBulkPrice(): Long = fields.sumOf { it.getBulkPrice() }
|
||||
|
||||
}
|
||||
|
||||
class Field(private val map: Map) {
|
||||
private val fieldPlots = mutableSetOf<Plot>()
|
||||
private val borderPlots = mutableSetOf<Pair<Plot, Plot>>()
|
||||
fun addPlot(plot: Plot) {
|
||||
fieldPlots.add(plot)
|
||||
}
|
||||
|
||||
fun expand(plot: Plot) {
|
||||
val (x, y) = plot.getPosition()
|
||||
val checkPositions = listOf(Pair(x - 1, y), Pair(x + 1, y), Pair(x, y - 1), Pair(x, y + 1))
|
||||
for ((checkX, checkY) in checkPositions) {
|
||||
if (checkX < 0 || checkX >= map.getWidth() || checkY < 0 || checkY >= map.getHeight()) {
|
||||
addBorder(plot, Plot(Pair(checkX, checkY), "."))
|
||||
continue
|
||||
}
|
||||
val checkedPlot = map.getPlot(checkX, checkY)
|
||||
if (plot.getType() == checkedPlot.getType()) {
|
||||
if (!checkedPlot.hasField()) {
|
||||
checkedPlot.addField(this)
|
||||
expand(checkedPlot)
|
||||
}
|
||||
} else {
|
||||
addBorder(plot, checkedPlot)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun addBorder(plot: Plot, checkedPlot: Plot) {
|
||||
borderPlots.add(Pair(plot, checkedPlot))
|
||||
}
|
||||
|
||||
fun getPrice(): Long {
|
||||
return getArea() * getCircumference()
|
||||
}
|
||||
|
||||
fun getArea(): Long = fieldPlots.size.toLong()
|
||||
fun getCircumference(): Long = borderPlots.size.toLong()
|
||||
fun getBulkPrice(): Long {
|
||||
return getArea() * getNumberOfSides()
|
||||
}
|
||||
|
||||
private fun getNumberOfSides(): Long {
|
||||
val leftBorders = borderPlots.filter { it.first.getPosition().first > it.second.getPosition().first}.map { it.first.getPosition() }
|
||||
val rightBorders = borderPlots.filter { it.first.getPosition().first < it.second.getPosition().first}.map { it.first.getPosition() }
|
||||
val topBorders = borderPlots.filter { it.first.getPosition().second > it.second.getPosition().second}.map { it.first.getPosition() }
|
||||
val bottomBorders = borderPlots.filter { it.first.getPosition().second < it.second.getPosition().second}.map { it.first.getPosition() }
|
||||
|
||||
return countSides(leftBorders) + countSides(rightBorders) + countSides(topBorders) + countSides(bottomBorders)
|
||||
}
|
||||
|
||||
private fun countSides(coordinates: List<Pair<Int, Int>>): Long {
|
||||
val visited = mutableSetOf<Pair<Int, Int>>()
|
||||
val coordinateSet = coordinates.toSet()
|
||||
var sideCount = 0
|
||||
val directions = listOf(Pair(1, 0), Pair(0, 1), Pair(-1, 0), Pair(0, -1))
|
||||
|
||||
fun dfs(coord: Pair<Int, Int>) {
|
||||
val stack = mutableListOf(coord)
|
||||
while (stack.isNotEmpty()) {
|
||||
val current = stack.removeLast()
|
||||
if (current in visited) continue
|
||||
visited.add(current)
|
||||
directions.map { (dx, dy) -> Pair(current.first + dx, current.second + dy) }
|
||||
.filter { it in coordinateSet && it !in visited }
|
||||
.forEach { stack.add(it) }
|
||||
}
|
||||
}
|
||||
for (coord in coordinates) {
|
||||
if (coord !in visited) {
|
||||
sideCount++
|
||||
dfs(coord)
|
||||
}
|
||||
}
|
||||
|
||||
return sideCount.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
class Plot(private val position: Pair<Int, Int>, private val type: String) {
|
||||
private var field: Field? = null
|
||||
fun addField(field: Field) {
|
||||
this.field = field
|
||||
field.addPlot(this)
|
||||
}
|
||||
|
||||
fun hasField(): Boolean = field != null
|
||||
fun getPosition(): Pair<Int, Int> = position
|
||||
fun getType(): String = type
|
||||
}
|
||||
|
||||
val grid = mutableListOf<List<String>>()
|
||||
while (scanner.hasNextLine()) {
|
||||
grid.add(scanner.nextLine().split("").filter { it.isNotEmpty() })
|
||||
}
|
||||
|
||||
val map = Map(grid.reversed())
|
||||
println("The price is: ${map.getPrice()}")
|
||||
println("The bulk price is ${map.getBulkPrice()}")
|
||||
2
day12.kts.testvalue
Normal file
2
day12.kts.testvalue
Normal file
@@ -0,0 +1,2 @@
|
||||
The price is: 1375476
|
||||
The bulk price is 821372
|
||||
127
simpletest.sh
Executable file
127
simpletest.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to show usage
|
||||
usage() {
|
||||
echo "Usage: $0 [-l] [write|validate]"
|
||||
echo " -l Use local input files (e.g., day01input.txt)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
LOCAL_MODE=0
|
||||
while getopts ":l" opt; do
|
||||
case $opt in
|
||||
l) LOCAL_MODE=1 ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
MODE=$1
|
||||
|
||||
# Function to fetch input
|
||||
fetch_input() {
|
||||
local script=$1
|
||||
local dayname
|
||||
dayname=$(basename "$script" .kts | tr 'a-z' 'A-Z')
|
||||
if [[ $LOCAL_MODE -eq 1 ]]; then
|
||||
# Fetch input from local files
|
||||
local input_file="${dayname,,}input.txt"
|
||||
if [[ -f "$input_file" ]]; then
|
||||
cat "$input_file"
|
||||
else
|
||||
echo "Error: Local input file $input_file not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Fetch input from environment variable
|
||||
local env_var="${dayname}INPUT"
|
||||
if [[ -z "${!env_var}" ]]; then
|
||||
echo "Error: Environment variable $env_var not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${!env_var}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Write mode
|
||||
if [[ "$MODE" == "write" ]]; then
|
||||
echo "Running in write mode..."
|
||||
|
||||
for script in *.kts; do
|
||||
test_file="${script}.testvalue"
|
||||
|
||||
echo "Processing $script..."
|
||||
input=$(fetch_input "$script")
|
||||
output=$(echo "$input" | kotlin "$script" 2>&1)
|
||||
|
||||
if [[ -f "$test_file" ]]; then
|
||||
if [[ "$output" == "$(cat "$test_file")" ]]; then
|
||||
echo "No change: $script"
|
||||
else
|
||||
echo "Output changed for $script"
|
||||
diff -u "$test_file" <(echo "$output")
|
||||
read -p "Update test value? (y/n) " update
|
||||
if [[ "$update" == "y" ]]; then
|
||||
echo "$output" > "$test_file"
|
||||
echo "Updated $test_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "$output" > "$test_file"
|
||||
echo "Created $test_file"
|
||||
fi
|
||||
done
|
||||
|
||||
for test_file in *.kts.testvalue; do
|
||||
script="${test_file%.testvalue}"
|
||||
if [[ ! -f "$script" ]]; then
|
||||
echo "Test file $test_file does not have a matching script."
|
||||
read -p "Delete $test_file? (y/n) " delete
|
||||
if [[ "$delete" == "y" ]]; then
|
||||
rm "$test_file"
|
||||
echo "Deleted $test_file"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate mode
|
||||
elif [[ "$MODE" == "validate" ]]; then
|
||||
echo "Running in validate mode..."
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
for test_file in *.kts.testvalue; do
|
||||
script="${test_file%.testvalue}"
|
||||
if [[ -f "$script" ]]; then
|
||||
input=$(fetch_input "$script")
|
||||
output=$(echo "$input" | kotlin "$script" 2>&1)
|
||||
if [[ "$output" == "$(cat "$test_file")" ]]; then
|
||||
echo "PASS: $script"
|
||||
((pass++))
|
||||
else
|
||||
echo "FAIL: $script"
|
||||
diff -u "$test_file" <(echo "$output")
|
||||
((fail++))
|
||||
fi
|
||||
else
|
||||
echo "Orphaned test file: $test_file (no matching script)"
|
||||
((fail++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Summary: $pass pass, $fail fail"
|
||||
|
||||
if [[ $fail -gt 0 ]]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
else
|
||||
usage
|
||||
fi
|
||||
Reference in New Issue
Block a user