Refactor day 4 of AoC 2023
This commit is contained in:
parent
827d6bf267
commit
e87578a619
13 changed files with 283 additions and 125 deletions
43
advent-of-code/2023/day_04/.gitignore
vendored
Normal file
43
advent-of-code/2023/day_04/.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.gradle
|
||||||
|
gradlew*
|
||||||
|
gradle
|
||||||
|
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
|
103
advent-of-code/2023/day_04/README.md
Normal file
103
advent-of-code/2023/day_04/README.md
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# Advent of Code 2023 day 4 solution in Kotlin
|
||||||
|
|
||||||
|
## Day 4: Scratchcards
|
||||||
|
|
||||||
|
[Task page](https://adventofcode.com/2023/day/4)
|
||||||
|
|
||||||
|
The gondola takes you up. Strangely, though, the ground doesn't seem to be coming with you;
|
||||||
|
you're not climbing a mountain. As the circle of Snow Island recedes below you,
|
||||||
|
an entire new landmass suddenly appears above you! The gondola carries you to the surface of the new island
|
||||||
|
and lurches into the station.
|
||||||
|
|
||||||
|
As you exit the gondola, the first thing you notice is that the air here is much warmer than it was on Snow Island.
|
||||||
|
It's also quite humid. Is this where the water source is?
|
||||||
|
|
||||||
|
The next thing you notice is an Elf sitting on the floor across the station in what
|
||||||
|
seems to bea pile of colorful square cards.
|
||||||
|
|
||||||
|
"Oh! Hello!" The Elf excitedly runs over to you. "How may I be of service?" You ask about water sources.
|
||||||
|
|
||||||
|
"I'm not sure; I just operate the gondola lift. That does sound like something we'd have, though - this is
|
||||||
|
Island Island, after all! I bet the gardener would know. He's on a different island, though - er,
|
||||||
|
the small kind surrounded by water, not the floating kind. We really need to come up with a better naming scheme.
|
||||||
|
Tell you what: if you can help me with something quick, I'll let you borrow my boat and you can go visit the gardener.
|
||||||
|
I got all these scratchcards as a gift, but I can't figure out what I've won."
|
||||||
|
|
||||||
|
The Elf leads you over to the pile of colorful cards. There, you discover dozens of scratchcards,
|
||||||
|
all with their opaque covering already scratched off. Picking one up, it looks like each card has
|
||||||
|
two lists of numbers separated by a vertical bar (|): a list of winning numbers and then a list of numbers you have.
|
||||||
|
You organize the information into a table (your puzzle input).
|
||||||
|
|
||||||
|
As far as the Elf has been able to figure out, you have to figure out which of the numbers you have appear in the list
|
||||||
|
of winning numbers. The first match makes the card worth one point and each match after the first doubles
|
||||||
|
the point value of that card.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
|
||||||
|
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
|
||||||
|
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
|
||||||
|
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
|
||||||
|
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
|
||||||
|
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, card 1 has five winning numbers (41, 48, 83, 86, and 17) and eight numbers you have
|
||||||
|
(83, 86, 6, 31, 17, 9, 48, and 53). Of the numbers you have, four of them (48, 83, 17, and 86) are winning numbers!
|
||||||
|
That means card 1 is worth 8 points (1 for the first match, then doubled three times for each of the three matches
|
||||||
|
after the first).
|
||||||
|
|
||||||
|
|
||||||
|
- Card 2 has two winning numbers (32 and 61), so it is worth 2 points.
|
||||||
|
- Card 3 has two winning numbers (1 and 21), so it is worth 2 points.
|
||||||
|
- Card 4 has one winning number (84), so it is worth 1 point.
|
||||||
|
- Card 5 has no winning numbers, so it is worth no points.
|
||||||
|
- Card 6 has no winning numbers, so it is worth no points.
|
||||||
|
|
||||||
|
So, in this example, the Elf's pile of scratchcards is worth 13 points.
|
||||||
|
|
||||||
|
Take a seat in the large pile of colorful cards. How many points are they worth in total?
|
||||||
|
|
||||||
|
|
||||||
|
## Part Two
|
||||||
|
|
||||||
|
Just as you're about to report your findings to the Elf, one of you realizes that the rules have actually been printed
|
||||||
|
on the back of every card this whole time.
|
||||||
|
|
||||||
|
There's no such thing as "points". Instead, scratchcards only cause you to win more scratchcards equal
|
||||||
|
to the number of winning numbers you have.
|
||||||
|
|
||||||
|
Specifically, you win copies of the scratchcards below the winning card equal to the number of matches.
|
||||||
|
So, if card 10 were to have 5 matching numbers, you would win one copy each of cards 11, 12, 13, 14, and 15.
|
||||||
|
|
||||||
|
Copies of scratchcards are scored like normal scratchcards and have the same card number as the card they copied.
|
||||||
|
So, if you win a copy of card 10 and it has 5 matching numbers, it would then win a copy of the same cards
|
||||||
|
that the original card 10 won: cards 11, 12, 13, 14, and 15. This process repeats until none of the copies cause you
|
||||||
|
to win any more cards. (Cards will never make you copy a card past the end of the table.)
|
||||||
|
|
||||||
|
This time, the above example goes differently:
|
||||||
|
|
||||||
|
```
|
||||||
|
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
|
||||||
|
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
|
||||||
|
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
|
||||||
|
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
|
||||||
|
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
|
||||||
|
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
|
||||||
|
```
|
||||||
|
|
||||||
|
- Card 1 has four matching numbers, so you win one copy each of the next four cards: cards 2, 3, 4, and 5.
|
||||||
|
- Your original card 2 has two matching numbers, so you win one copy each of cards 3 and 4.
|
||||||
|
- Your copy of card 2 also wins one copy each of cards 3 and 4.
|
||||||
|
- Your four instances of card 3 (one original and three copies) have two matching numbers, so you win four copies each of cards 4 and 5.
|
||||||
|
- Your eight instances of card 4 (one original and seven copies) have one matching number, so you win eight copies of card 5.
|
||||||
|
- Your fourteen instances of card 5 (one original and thirteen copies) have no matching numbers and win no more cards.
|
||||||
|
- Your one instance of card 6 (one original) has no matching numbers and wins no more cards.
|
||||||
|
|
||||||
|
Once all of the originals and copies have been processed, you end up with 1 instance of card 1, 2 instances of card 2,
|
||||||
|
4 instances of card 3, 8 instances of card 4, 14 instances of card 5, and 1 instance of card 6. In total,
|
||||||
|
this example pile of scratchcards causes you to ultimately have 30 scratchcards!
|
||||||
|
|
||||||
|
Process all of the original and copied scratchcards until no more scratchcards are won. Including the original set
|
||||||
|
of scratchcards, how many total scratchcards do you end up with?
|
18
advent-of-code/2023/day_04/build.gradle.kts
Normal file
18
advent-of-code/2023/day_04/build.gradle.kts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "2.0.21"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "space.comfycamp"
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(kotlin("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
1
advent-of-code/2023/day_04/gradle.properties
Normal file
1
advent-of-code/2023/day_04/gradle.properties
Normal file
|
@ -0,0 +1 @@
|
||||||
|
kotlin.code.style=official
|
2
advent-of-code/2023/day_04/settings.gradle.kts
Normal file
2
advent-of-code/2023/day_04/settings.gradle.kts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
rootProject.name = "day_04"
|
||||||
|
|
79
advent-of-code/2023/day_04/src/main/kotlin/Cards.kt
Normal file
79
advent-of-code/2023/day_04/src/main/kotlin/Cards.kt
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package space.comfycamp
|
||||||
|
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/** Part 1. */
|
||||||
|
fun countPoints(lines: List<String>): Int {
|
||||||
|
val cards = parseCards(lines)
|
||||||
|
return cards.sumOf { it.getPoints() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Part 2. */
|
||||||
|
fun countCards(lines: List<String>): Int {
|
||||||
|
val cards = parseCards(lines)
|
||||||
|
|
||||||
|
for ((idx, card) in cards.withIndex()) {
|
||||||
|
val maxIdx = min(idx+card.countMatchingNumbers(), cards.size-1)
|
||||||
|
for (i in idx+1..maxIdx) {
|
||||||
|
cards[i].instances += card.instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cards.sumOf { it.instances }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCards(lines: List<String>): MutableList<Card> {
|
||||||
|
val cards = mutableListOf<Card>()
|
||||||
|
|
||||||
|
for (line in lines) {
|
||||||
|
val (_, numbers) = line.split(": ")
|
||||||
|
if (numbers.isEmpty()) continue
|
||||||
|
|
||||||
|
val (winningNumbers, selectedNumbers) = numbers.split(" | ")
|
||||||
|
cards.add(Card(
|
||||||
|
strToInts(winningNumbers),
|
||||||
|
strToInts(selectedNumbers),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cards
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun strToInts(s: String): MutableList<Int> {
|
||||||
|
return s
|
||||||
|
.split(" ")
|
||||||
|
.filter{it.isNotEmpty()}
|
||||||
|
.map{it.toInt()}
|
||||||
|
.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Card(
|
||||||
|
val winningNumbers: MutableList<Int> = mutableListOf(),
|
||||||
|
val selectedNumbers: MutableList<Int> = mutableListOf(),
|
||||||
|
var instances: Int = 1,
|
||||||
|
) {
|
||||||
|
fun countMatchingNumbers(): Int {
|
||||||
|
return selectedNumbers
|
||||||
|
.filter{winningNumbers.contains(it)}
|
||||||
|
.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPoints(): Int {
|
||||||
|
val count = countMatchingNumbers()
|
||||||
|
return if (count == 0) 0 else intPow(2, count-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I haven't found a function to raise an integer to a power.
|
||||||
|
* You should not use powers less than 0 :)
|
||||||
|
*/
|
||||||
|
fun intPow(base: Int, exponent: Int): Int {
|
||||||
|
var result = 1
|
||||||
|
|
||||||
|
for (i in 1..exponent) {
|
||||||
|
result *= base
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
12
advent-of-code/2023/day_04/src/main/kotlin/Main.kt
Normal file
12
advent-of-code/2023/day_04/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 = countPoints(lines)
|
||||||
|
println("Part 1: $res1")
|
||||||
|
|
||||||
|
val res2 = countCards(lines)
|
||||||
|
println("Part 2: $res2")
|
||||||
|
}
|
25
advent-of-code/2023/day_04/src/test/kotlin/CardsTest.kt
Normal file
25
advent-of-code/2023/day_04/src/test/kotlin/CardsTest.kt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import space.comfycamp.countCards
|
||||||
|
import space.comfycamp.countPoints
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class CardsTest {
|
||||||
|
val lines = listOf(
|
||||||
|
"Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53",
|
||||||
|
"Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19",
|
||||||
|
"Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1",
|
||||||
|
"Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83",
|
||||||
|
"Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36",
|
||||||
|
"Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCountPoints() {
|
||||||
|
assertEquals(13, countPoints(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCountCards() {
|
||||||
|
assertEquals(30, countCards(lines))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* I haven't found a function to raise an integer to a power.
|
|
||||||
* You should not use powers less than 0 :)
|
|
||||||
*/
|
|
||||||
fun intPow(base: Int, exponent: Int): Int {
|
|
||||||
var result = 1
|
|
||||||
|
|
||||||
for (i in 1..exponent) {
|
|
||||||
result *= base
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
class Solution04 {
|
|
||||||
/** Part 1. */
|
|
||||||
fun getPointCount(lines: List<String>): Int {
|
|
||||||
val cards = parseCards(lines)
|
|
||||||
return cards
|
|
||||||
.map{it.getPoints()}
|
|
||||||
.reduce{acc, points -> acc + points}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Part 2. */
|
|
||||||
fun getCardCount(lines: List<String>): Int {
|
|
||||||
val cards = parseCards(lines)
|
|
||||||
|
|
||||||
for ((idx, card) in cards.withIndex()) {
|
|
||||||
// This is the last card.
|
|
||||||
if (idx == cards.size - 1) break
|
|
||||||
|
|
||||||
val matchingNumberCount = card.getMatchingNumberCount()
|
|
||||||
if (matchingNumberCount == 0) continue
|
|
||||||
|
|
||||||
for (i in idx+1..idx+matchingNumberCount) {
|
|
||||||
if (i >= cards.size) break
|
|
||||||
|
|
||||||
cards[i].instances += card.instances
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cards
|
|
||||||
.map{it.instances}
|
|
||||||
.reduce{acc, instances -> acc + instances}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseCards(lines: List<String>): MutableList<Card> {
|
|
||||||
val cards = mutableListOf<Card>()
|
|
||||||
|
|
||||||
for (line in lines) {
|
|
||||||
val (_, numbers) = line.split(": ")
|
|
||||||
if (numbers.isEmpty()) continue
|
|
||||||
|
|
||||||
val (winningNumbers, selectedNumbers) = numbers.split(" | ")
|
|
||||||
cards.add(Card(
|
|
||||||
strToInts(winningNumbers),
|
|
||||||
strToInts(selectedNumbers),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cards
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun strToInts(s: String): MutableList<Int> {
|
|
||||||
return s
|
|
||||||
.split(" ")
|
|
||||||
.filter{it.isNotEmpty()}
|
|
||||||
.map{it.toInt()}
|
|
||||||
.toMutableList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Card(
|
|
||||||
val winningNumbers: MutableList<Int> = mutableListOf(),
|
|
||||||
val selectedNumbers: MutableList<Int> = mutableListOf(),
|
|
||||||
/** Used only in part 2. */
|
|
||||||
var instances: Int = 1,
|
|
||||||
) {
|
|
||||||
fun getMatchingNumberCount(): Int {
|
|
||||||
return selectedNumbers
|
|
||||||
.filter{winningNumbers.contains(it)}
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
fun getPoints(): Int {
|
|
||||||
val count = getMatchingNumberCount()
|
|
||||||
return if (count == 0) 0 else intPow(2, count-1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
|
|
||||||
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
|
|
||||||
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
|
|
||||||
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
|
|
||||||
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
|
|
||||||
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
|
|
|
@ -1,32 +0,0 @@
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class Solution04Test {
|
|
||||||
@Test
|
|
||||||
fun testGetPointCount() {
|
|
||||||
val text = ResourceReader().readFile("day-04/test.txt")
|
|
||||||
val res = Solution04().getPointCount(text)
|
|
||||||
assertEquals(13, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun solvePart1() {
|
|
||||||
val text = ResourceReader().readFile("day-04/input.txt")
|
|
||||||
val res = Solution04().getPointCount(text)
|
|
||||||
println(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetCardCount() {
|
|
||||||
val text = ResourceReader().readFile("day-04/test.txt")
|
|
||||||
val res = Solution04().getCardCount(text)
|
|
||||||
assertEquals(30, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun solvePart2() {
|
|
||||||
val text = ResourceReader().readFile("day-04/input.txt")
|
|
||||||
val res = Solution04().getCardCount(text)
|
|
||||||
println(res)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue