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:
|
jobs:
|
||||||
example-action:
|
example-action:
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'corretto' # See 'Supported distributions' for available options
|
distribution: 'corretto'
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- uses: https://github.com/fwilhe2/setup-kotlin@main
|
- uses: https://github.com/fwilhe2/setup-kotlin@main
|
||||||
|
- name: Run tests
|
||||||
|
run: ./simpletest.sh validate
|
||||||
- name: Solutions
|
- name: Solutions
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
for script in $(ls *.kts | sort -r); do
|
for script in $(ls *.kts | sort -r); do
|
||||||
echo "Running $script" | tee -a solutions-output.txt
|
dayname=$(basename $script .kts | tr 'a-z' 'A-Z')INPUT
|
||||||
kotlin $script 2>&1 | tee -a solutions-output.txt
|
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
|
done
|
||||||
- name: Upload Solutions Output
|
- name: Upload Solutions Output
|
||||||
uses: actions/upload-artifact@v3
|
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 java.util.Scanner
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
val scanner = Scanner(File("day01input.txt"))
|
val scanner = Scanner(System.`in`)
|
||||||
val firstList = mutableListOf<Int>()
|
val firstList = mutableListOf<Int>()
|
||||||
val secondList = mutableListOf<Int>()
|
val secondList = mutableListOf<Int>()
|
||||||
var secondMap = mutableMapOf<Int, 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 java.util.Scanner
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
val scanner = Scanner(File("day02input.txt"))
|
val scanner = Scanner(System.`in`)
|
||||||
var safelines = 0
|
var safelines = 0
|
||||||
var dampenedsafelines = 0
|
var dampenedsafelines = 0
|
||||||
while (scanner.hasNextLine()) {
|
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