
🔥 매년 12월 1일부터 12월 25일까지 한 문제씩 프로그래밍 퍼즐을 제공하는 사이트다. Eric Wastl이 2015년에 만든 사이트로, 재림절 달력(Advent calendar)을 매일 열어보듯이 크리스마스까지 꾸준히 문제를 해결해나간다는 컨셉이다. 퍼즐은 UTC-5 기준으로 자정, 한국시간으로는 오후 2시에 공개된다.
출처 : 나무위키 - Advent of Code
Advent of Code가 2023년 돌아왔습니다! 25일간 프로그래밍 퍼즐을 풀어봅시다.
Jetbrains는 이번에도 어김없이 Kotlin을 통해 25일간의 AoC에 참가하면 코틀린 독점 상품을 받을 수 있는 기회를 준다고 합니다!
자세한 내용은 코틀린 블로그에 개제된 Tackle Advent of Code 2023 With Kotlin and Win Prizes! 를 참고하세요.
오늘은 세 번째 문제인 Gear Ratio 문제를 풀어봤습니다.
이번 문제는 주말에 나왔어서 상당히 난이도가 있는 문제였습니다. 제가 2D Matirx에 상당히 약해서, 문제를 푸는데 꽤 어려웠습니다 😂
고장난 엔진을 고쳐야 합니다.
엔진 도식(퍼즐)은 아래와 같은 구조로 되어 있습니다.
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
. 은 빈 공간입니다.. 을 제외한 기호에 붙어 있는 숫자는 부품 번호입니다.* 는 467, 35와 붙어 있으므로, 합계에 467와 35을 더합니다.우선 기호의 위치를 파악하고 기호에 붙어있는 숫자들을 파악해 합계에 합산하려고 합니다.
기호의 위치를 파악하면, 그 주변에 있는 숫자들의 유무와 위치에 따라 로직을 다르게 구현했습니다.

“35”.drop(1) + “57” = “357”
fun String.filterNumber(idx: Int): List<Int> {
val left = dfs(this, idx, true)
val right = dfs(this, idx, false)
return if (left.isEmpty() && right.isEmpty()) {
listOf(
dfs(this, idx - 1, true),
dfs(this, idx + 1, false)
).mapNotNull { it.toIntOrNull() }
} else {
listOf(
left.dropLast(1) + right
).mapNotNull { it.toIntOrNull() }
}
}
fun dfs(str: String, index: Int, left: Boolean): String {
val c = str.getOrNull(index)
if (c == null || c == '.') return ""
return if (left) {
dfs(str, index - 1, true) + c
} else {
c + dfs(str, index + 1, false)
}
}
기호 좌/우에 있는 숫자를 탐색합니다.

a. 위에 구현한 dfs 함수를 사용하면 된다. 기호의 좌/우를 시작점으로 구현한 dfs 함수를 호출하면 끝!
fun part1(input: List<String>): Int {
var sum = 0
input.forEachIndexed { i, str ->
str.forEachIndexed { j, c ->
if (input[i][j] != '.' && input[i][j].isDigit().not()) {
val num1 = input.getOrNull(i - 1)?.filterNumber(j).orEmpty()
val num2 = dfs(input.getOrNull(i).orEmpty(), j - 1, true).toIntOrNull()
val num3 = dfs(input.getOrNull(i).orEmpty(), j + 1, false).toIntOrNull()
val num4 = input.getOrNull(i + 1)?.filterNumber(j).orEmpty()
val list = num1 + listOfNotNull(num2, num3) + num4
sum += list.sum()
}
}
}
return sum
}
solve { sumOf { it.sum() } }
private val digits = "([0-9]+)".toRegex()
private val symbols = "([^0-9.])".toRegex()
private fun Sequence<String>.solve(
sum: Sequence<List<Int>>.() -> Int,
) = windowed(3) { lines ->
symbols.findAll(lines[1])
.map(MatchResult::range)
.map(IntRange::first)
.map { symbol ->
lines.flatMap(digits::findAll)
.map { (it.range.first - 1)..(it.range.last + 1) to it.groupValues[1].toInt() }
.filter { (target, _) -> symbol in target }.map { (_, number) -> number }
}
}.flatten().run(sum)
windowed() 를 이용했습니다.lines[1]) 문자열에 있는 기호들을 symbols 정규식을 통해 찾습니다.windowed()를 이용해 가져온 3줄에 있는 숫자 위치를 파악합니다.이번에는 * 기호에 2개의 숫자가 붙어 있는 것만 합계에 합산합니다.
이 때 2개의 숫자는 곱한 값을 합계에 합산하도록 합니다.
part 1 코드가 * 기호 기준으로 동작하므로 조건 2가지만 변경하면 됩니다!
* 만 비교하도록 변경fun part2(input: List<String>): Int {
var sum = 0
input.forEachIndexed { i, str ->
str.forEachIndexed { j, c ->
if (input[i][j] == '*') {
val num1 = input.getOrNull(i - 1)?.filterNumber(j).orEmpty()
val num2 = dfs(input.getOrNull(i).orEmpty(), j - 1, true).toIntOrNull()
val num3 = dfs(input.getOrNull(i).orEmpty(), j + 1, false).toIntOrNull()
val num4 = input.getOrNull(i + 1)?.filterNumber(j).orEmpty()
val list = num1 + listOfNotNull(num2, num3) + num4
if (list.size > 1) {
sum += list.first() * list.last()
}
}
}
}
return sum
}
solve { sumOf { it.sum() } }
Part 1에 있는 Sequence<String>.solve() 를 이용해 List를 가져옵니다. 리스트의 사이즈가 2 이상인 경우에만 해당 값들을 곱해 합계에 합산합니다.