Refactor day 3 of AoC 2023
This commit is contained in:
parent
8a9f518b5b
commit
827d6bf267
12 changed files with 317 additions and 194 deletions
43
advent-of-code/2023/day_03/.gitignore
vendored
Normal file
43
advent-of-code/2023/day_03/.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.gradle
|
||||||
|
gradle
|
||||||
|
gradlew*
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
85
advent-of-code/2023/day_03/README.md
Normal file
85
advent-of-code/2023/day_03/README.md
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Advent of Code 2023 day 3 solution in Kotlin
|
||||||
|
|
||||||
|
## Day 3: Gear Ratios
|
||||||
|
|
||||||
|
[Task page](https://adventofcode.com/2023/day/3)
|
||||||
|
|
||||||
|
You and the Elf eventually reach a gondola lift station; he says the gondola lift will take you up to the water source,
|
||||||
|
but this is as far as he can bring you. You go inside.
|
||||||
|
|
||||||
|
It doesn't take long to find the gondolas, but there seems to be a problem: they're not moving.
|
||||||
|
|
||||||
|
"Aaah!"
|
||||||
|
|
||||||
|
You turn around to see a slightly-greasy Elf with a wrench and a look of surprise. "Sorry, I wasn't expecting anyone!
|
||||||
|
The gondola lift isn't working right now; it'll still be a while before I can fix it." You offer to help.
|
||||||
|
|
||||||
|
The engineer explains that an engine part seems to be missing from the engine, but nobody can figure out which one.
|
||||||
|
If you can add up all the part numbers in the engine schematic, it should be easy to work out which part is missing.
|
||||||
|
|
||||||
|
The engine schematic (your puzzle input) consists of a visual representation of the engine.
|
||||||
|
There are lots of numbers and symbols you don't really understand, but apparently any number adjacent to a symbol,
|
||||||
|
even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.)
|
||||||
|
|
||||||
|
Here is an example engine schematic:
|
||||||
|
|
||||||
|
```
|
||||||
|
467..114..
|
||||||
|
...*......
|
||||||
|
..35..633.
|
||||||
|
......#...
|
||||||
|
617*......
|
||||||
|
.....+.58.
|
||||||
|
..592.....
|
||||||
|
......755.
|
||||||
|
...$.*....
|
||||||
|
.664.598..
|
||||||
|
```
|
||||||
|
|
||||||
|
In this schematic, two numbers are not part numbers because they are not adjacent to a symbol: 114 (top right)
|
||||||
|
and 58 (middle right). Every other number is adjacent to a symbol and so is a part number; their sum is 4361.
|
||||||
|
|
||||||
|
Of course, the actual engine schematic is much larger.
|
||||||
|
What is the sum of all of the part numbers in the engine schematic?
|
||||||
|
|
||||||
|
|
||||||
|
## Part Two
|
||||||
|
|
||||||
|
The engineer finds the missing part and installs it in the engine! As the engine springs to life,
|
||||||
|
you jump in the closest gondola, finally ready to ascend to the water source.
|
||||||
|
|
||||||
|
You don't seem to be going very fast, though. Maybe something is still wrong? Fortunately,
|
||||||
|
the gondola has a phone labeled "help", so you pick it up and the engineer answers.
|
||||||
|
|
||||||
|
Before you can explain the situation, she suggests that you look out the window. There stands the engineer,
|
||||||
|
holding a phone in one hand and waving with the other. You're going so slowly that you haven't even left the station.
|
||||||
|
You exit the gondola.
|
||||||
|
|
||||||
|
The missing part wasn't the only issue - one of the gears in the engine is wrong.
|
||||||
|
A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is the result
|
||||||
|
of multiplying those two numbers together.
|
||||||
|
|
||||||
|
This time, you need to find the gear ratio of every gear and add them all up so that the engineer can figure out
|
||||||
|
which gear needs to be replaced.
|
||||||
|
|
||||||
|
Consider the same engine schematic again:
|
||||||
|
|
||||||
|
```
|
||||||
|
467..114..
|
||||||
|
...*......
|
||||||
|
..35..633.
|
||||||
|
......#...
|
||||||
|
617*......
|
||||||
|
.....+.58.
|
||||||
|
..592.....
|
||||||
|
......755.
|
||||||
|
...$.*....
|
||||||
|
.664.598..
|
||||||
|
```
|
||||||
|
|
||||||
|
In this schematic, there are two gears. The first is in the top left; it has part numbers 467 and 35,
|
||||||
|
so its gear ratio is 16345. The second gear is in the lower right; its gear ratio is 451490.
|
||||||
|
(The * adjacent to 617 is not a gear because it is only adjacent to one part number.)
|
||||||
|
Adding up all of the gear ratios produces 467835.
|
||||||
|
|
||||||
|
What is the sum of all of the gear ratios in your engine schematic?
|
18
advent-of-code/2023/day_03/build.gradle.kts
Normal file
18
advent-of-code/2023/day_03/build.gradle.kts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "2.0.21"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "space.comfycamp"
|
||||||
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(kotlin("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
1
advent-of-code/2023/day_03/gradle.properties
Normal file
1
advent-of-code/2023/day_03/gradle.properties
Normal file
|
@ -0,0 +1 @@
|
||||||
|
kotlin.code.style=official
|
2
advent-of-code/2023/day_03/settings.gradle.kts
Normal file
2
advent-of-code/2023/day_03/settings.gradle.kts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
rootProject.name = "day_03"
|
||||||
|
|
127
advent-of-code/2023/day_03/src/main/kotlin/Engine.kt
Normal file
127
advent-of-code/2023/day_03/src/main/kotlin/Engine.kt
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package space.comfycamp
|
||||||
|
|
||||||
|
/** Part 1. */
|
||||||
|
fun getSumOfParts(lines: List<String>): Int {
|
||||||
|
var sum = 0
|
||||||
|
var buf = ""
|
||||||
|
var isBufferAdjacent = false
|
||||||
|
|
||||||
|
for (i in lines.indices) {
|
||||||
|
for (j in lines[i].indices) {
|
||||||
|
if (lines[i][j].isDigit()) {
|
||||||
|
buf += lines[i][j]
|
||||||
|
|
||||||
|
isBufferAdjacent = isBufferAdjacent || getAdjacentSymbols(lines, i, j, ::isSymbol).isNotEmpty()
|
||||||
|
|
||||||
|
// Skip number parsing if it's not the end of string.
|
||||||
|
if (j < lines[0].length - 1) continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.isNotEmpty() && isBufferAdjacent) {
|
||||||
|
sum += buf.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = ""
|
||||||
|
isBufferAdjacent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Part 2. */
|
||||||
|
fun getGearRatioSum(lines: List<String>): Int {
|
||||||
|
// 3d list of adjacent numbers for every asterisk.
|
||||||
|
val adjacentNumbers = List(lines.size) {
|
||||||
|
List(lines[0].length) {
|
||||||
|
mutableListOf<Int>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = ""
|
||||||
|
val adjacentGears = mutableListOf<Coordinates>()
|
||||||
|
|
||||||
|
for (i in lines.indices) {
|
||||||
|
for (j in lines[i].indices) {
|
||||||
|
if (lines[i][j].isDigit()) {
|
||||||
|
buf += lines[i][j]
|
||||||
|
|
||||||
|
// Remember every asterisk that is adjacent to the current digit,
|
||||||
|
for (c in getAdjacentSymbols(lines, i, j, ::isAsterisk)) {
|
||||||
|
if (!adjacentGears.contains(c)) adjacentGears.add(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j < lines[0].length - 1) continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.isNotEmpty()) {
|
||||||
|
val num = buf.toInt()
|
||||||
|
for (gear in adjacentGears) {
|
||||||
|
// Add current buffer to the list of adjacent numbers.
|
||||||
|
adjacentNumbers[gear.i][gear.j].add(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = ""
|
||||||
|
adjacentGears.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum = 0
|
||||||
|
|
||||||
|
for (i in adjacentNumbers.indices) {
|
||||||
|
for (j in adjacentNumbers[i].indices) {
|
||||||
|
if (adjacentNumbers[i][j].size == 2) {
|
||||||
|
sum += adjacentNumbers[i][j][0] * adjacentNumbers[i][j][1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAdjacentSymbols(lines: List<String>, i: Int, j: Int, condition: (Char) -> Boolean): List<Coordinates> {
|
||||||
|
val jMax = lines[0].length - 1
|
||||||
|
val res = mutableListOf<Coordinates>()
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
// Top
|
||||||
|
if (condition(lines[i - 1][j])) res.add(Coordinates(i - 1, j))
|
||||||
|
|
||||||
|
// Top left
|
||||||
|
if (j > 0 && condition(lines[i - 1][j - 1])) res.add(Coordinates(i - 1, j - 1))
|
||||||
|
|
||||||
|
// Top right
|
||||||
|
if (j < jMax && condition(lines[i - 1][j + 1])) res.add(Coordinates(i - 1, j + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < lines.size - 1) {
|
||||||
|
// Bottom
|
||||||
|
if (condition(lines[i + 1][j])) res.add(Coordinates(i + 1, j))
|
||||||
|
|
||||||
|
// Bottom left
|
||||||
|
if (j > 0 && condition(lines[i + 1][j - 1])) res.add(Coordinates(i + 1, j - 1))
|
||||||
|
|
||||||
|
// Bottom right
|
||||||
|
if (j < jMax && condition(lines[i + 1][j + 1])) res.add(Coordinates(i + 1, j + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left
|
||||||
|
if (j > 0 && condition(lines[i][j - 1])) res.add(Coordinates(i, j - 1))
|
||||||
|
|
||||||
|
// Right
|
||||||
|
if (j < jMax && condition(lines[i][j + 1])) res.add(Coordinates(i, j + 1))
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSymbol(char: Char): Boolean {
|
||||||
|
if (char.isDigit()) return false
|
||||||
|
return char != '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAsterisk(char: Char): Boolean {
|
||||||
|
return char == '*'
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Coordinates(var i: Int, var j: Int)
|
12
advent-of-code/2023/day_03/src/main/kotlin/Main.kt
Normal file
12
advent-of-code/2023/day_03/src/main/kotlin/Main.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package space.comfycamp
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val resource = object{}.javaClass.getResource("/input.txt")!!
|
||||||
|
val lines = resource.readText().trim().lines()
|
||||||
|
|
||||||
|
val res1 = getSumOfParts(lines)
|
||||||
|
println("Part 1: $res1")
|
||||||
|
|
||||||
|
val res2 = getGearRatioSum(lines)
|
||||||
|
println("Part 2: $res2")
|
||||||
|
}
|
29
advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt
Normal file
29
advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import space.comfycamp.getGearRatioSum
|
||||||
|
import space.comfycamp.getSumOfParts
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class EngineTest {
|
||||||
|
val lines = listOf(
|
||||||
|
"467..114..",
|
||||||
|
"...*......",
|
||||||
|
"..35..633.",
|
||||||
|
"......#...",
|
||||||
|
"617*......",
|
||||||
|
".....+.58.",
|
||||||
|
"..592.....",
|
||||||
|
"......755.",
|
||||||
|
"...$.*....",
|
||||||
|
".664.598..",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetSumOfParts() {
|
||||||
|
assertEquals(4361, getSumOfParts(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetGearRatioSum() {
|
||||||
|
assertEquals(467835, getGearRatioSum(lines))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,147 +0,0 @@
|
||||||
class Solution03(private val lines: List<String>) {
|
|
||||||
|
|
||||||
/** Maximum value of J = number of columns - 1.
|
|
||||||
* If j is less than jMax, then j can be safely incremented.
|
|
||||||
* */
|
|
||||||
private val jMax = lines[0].length - 1
|
|
||||||
|
|
||||||
/** Array to simplify character classification. */
|
|
||||||
private val digits = arrayOf('1', '2', '3', '4', '5', '6', '7', '8', '9', '0')
|
|
||||||
|
|
||||||
/** Part 1. */
|
|
||||||
fun getSumOfParts(): Int {
|
|
||||||
var sum = 0
|
|
||||||
var buf = ""
|
|
||||||
var isBufferAdjacent = false
|
|
||||||
|
|
||||||
for (i in lines.indices) {
|
|
||||||
for (j in lines[i].indices) {
|
|
||||||
if (digits.contains(lines[i][j])) {
|
|
||||||
buf += lines[i][j]
|
|
||||||
|
|
||||||
// Mark current buffer as adjacent to a symbol if necessary.
|
|
||||||
if (getAdjacentSymbols(i, j, ::isSymbol).isNotEmpty()) {
|
|
||||||
isBufferAdjacent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip number parsing if it's not the end of string.
|
|
||||||
if (j < jMax) continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.isNotEmpty() && isBufferAdjacent) {
|
|
||||||
sum += buf.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = ""
|
|
||||||
isBufferAdjacent = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Part 2. */
|
|
||||||
fun getGearRatioSum(): Int {
|
|
||||||
/**
|
|
||||||
* 2d array with a list of adjacent numbers for every asterisk.
|
|
||||||
*/
|
|
||||||
val adjacentNumbers = Array(lines.size) {
|
|
||||||
Array(lines[0].length){
|
|
||||||
mutableListOf<Int>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf = ""
|
|
||||||
val adjacentGears = mutableListOf<Coordinates>()
|
|
||||||
|
|
||||||
for (i in lines.indices) {
|
|
||||||
for (j in lines[i].indices) {
|
|
||||||
if (digits.contains(lines[i][j])) {
|
|
||||||
buf += lines[i][j]
|
|
||||||
|
|
||||||
// For every asterisk that is adjacent to the current digit
|
|
||||||
for (c in getAdjacentSymbols(i, j, ::isAsterisk)) {
|
|
||||||
// Check if we already added asterisk to our temporary list
|
|
||||||
var exists = false
|
|
||||||
for (gear in adjacentGears) {
|
|
||||||
if (gear.i == c.i && gear.j == c.j) exists = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new asterisk
|
|
||||||
if (!exists) adjacentGears.add(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j < jMax) continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.isNotEmpty()) {
|
|
||||||
val num = buf.toInt()
|
|
||||||
for (adjacentGear in adjacentGears) {
|
|
||||||
// Add current buffer to the list of adjacent numbers.
|
|
||||||
adjacentNumbers[adjacentGear.i][adjacentGear.j].add(num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = ""
|
|
||||||
adjacentGears.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum = 0
|
|
||||||
|
|
||||||
for (i in adjacentNumbers.indices) {
|
|
||||||
for (j in adjacentNumbers[i].indices) {
|
|
||||||
if (adjacentNumbers[i][j].size == 2) {
|
|
||||||
sum += adjacentNumbers[i][j][0] * adjacentNumbers[i][j][1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAdjacentSymbols(i: Int, j: Int, condition: (i: Int, j: Int) -> Boolean): List<Coordinates> {
|
|
||||||
val res = mutableListOf<Coordinates>()
|
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
// Top
|
|
||||||
if (condition(i-1, j)) res.add(Coordinates(i-1, j))
|
|
||||||
|
|
||||||
// Top left
|
|
||||||
if (j > 0 && condition(i-1, j-1)) res.add(Coordinates(i-1, j-1))
|
|
||||||
|
|
||||||
// Top right
|
|
||||||
if (j < jMax && condition(i-1, j+1)) res.add(Coordinates(i-1, j+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < lines.size - 1) {
|
|
||||||
// Bottom
|
|
||||||
if (condition(i+1, j)) res.add(Coordinates(i+1, j))
|
|
||||||
|
|
||||||
// Bottom left
|
|
||||||
if (j > 0 && condition(i+1, j-1)) res.add(Coordinates(i+1, j-1))
|
|
||||||
|
|
||||||
// Bottom right
|
|
||||||
if (j < jMax && condition(i+1,j+1)) res.add(Coordinates(i+1, j+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left
|
|
||||||
if (j > 0 && condition(i, j-1)) res.add(Coordinates(i, j-1))
|
|
||||||
|
|
||||||
// Right
|
|
||||||
if (j < jMax && condition(i, j+1)) res.add(Coordinates(i, j+1))
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isSymbol(i: Int, j: Int): Boolean {
|
|
||||||
if (digits.contains(lines[i][j])) return false
|
|
||||||
return lines[i][j] != '.'
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAsterisk(i: Int, j: Int): Boolean {
|
|
||||||
return lines[i][j] == '*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Coordinates(var i: Int, var j: Int)
|
|
|
@ -1,10 +0,0 @@
|
||||||
467..114..
|
|
||||||
...*......
|
|
||||||
..35..633.
|
|
||||||
......#...
|
|
||||||
617*......
|
|
||||||
.....+.58.
|
|
||||||
..592.....
|
|
||||||
......755.
|
|
||||||
...$.*....
|
|
||||||
.664.598..
|
|
|
@ -1,37 +0,0 @@
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class Solution03Test {
|
|
||||||
@Test
|
|
||||||
fun testGetSumOfParts() {
|
|
||||||
val text = ResourceReader().readFile("day-03/test.txt")
|
|
||||||
val instance = Solution03(text)
|
|
||||||
val res = instance.getSumOfParts()
|
|
||||||
assertEquals(4361, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun solvePart1() {
|
|
||||||
val text = ResourceReader().readFile("day-03/input.txt")
|
|
||||||
val instance = Solution03(text)
|
|
||||||
val res = instance.getSumOfParts()
|
|
||||||
println(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetGearRatio() {
|
|
||||||
val text = ResourceReader().readFile("day-03/test.txt")
|
|
||||||
val instance = Solution03(text)
|
|
||||||
val res = instance.getGearRatioSum()
|
|
||||||
assertEquals(467835, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun solvePart2() {
|
|
||||||
val text = ResourceReader().readFile("day-03/input.txt")
|
|
||||||
val instance = Solution03(text)
|
|
||||||
val res = instance.getGearRatioSum()
|
|
||||||
println(res)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue