diff --git a/advent-of-code/2023/day_04/.gitignore b/advent-of-code/2023/day_04/.gitignore new file mode 100644 index 0000000..261ed2a --- /dev/null +++ b/advent-of-code/2023/day_04/.gitignore @@ -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 diff --git a/advent-of-code/2023/day_04/README.md b/advent-of-code/2023/day_04/README.md new file mode 100644 index 0000000..5554c8a --- /dev/null +++ b/advent-of-code/2023/day_04/README.md @@ -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? diff --git a/advent-of-code/2023/day_04/build.gradle.kts b/advent-of-code/2023/day_04/build.gradle.kts new file mode 100644 index 0000000..6397ac9 --- /dev/null +++ b/advent-of-code/2023/day_04/build.gradle.kts @@ -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() +} diff --git a/advent-of-code/2023/day_04/gradle.properties b/advent-of-code/2023/day_04/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/advent-of-code/2023/day_04/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/advent-of-code/2023/day_04/settings.gradle.kts b/advent-of-code/2023/day_04/settings.gradle.kts new file mode 100644 index 0000000..dee997c --- /dev/null +++ b/advent-of-code/2023/day_04/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "day_04" + diff --git a/advent-of-code/2023/day_04/src/main/kotlin/Cards.kt b/advent-of-code/2023/day_04/src/main/kotlin/Cards.kt new file mode 100644 index 0000000..213a994 --- /dev/null +++ b/advent-of-code/2023/day_04/src/main/kotlin/Cards.kt @@ -0,0 +1,79 @@ +package space.comfycamp + +import kotlin.math.min + +/** Part 1. */ +fun countPoints(lines: List): Int { + val cards = parseCards(lines) + return cards.sumOf { it.getPoints() } +} + +/** Part 2. */ +fun countCards(lines: List): 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): MutableList { + val cards = mutableListOf() + + 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 { + return s + .split(" ") + .filter{it.isNotEmpty()} + .map{it.toInt()} + .toMutableList() +} + +data class Card( + val winningNumbers: MutableList = mutableListOf(), + val selectedNumbers: MutableList = 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 +} diff --git a/advent-of-code/2023/day_04/src/main/kotlin/Main.kt b/advent-of-code/2023/day_04/src/main/kotlin/Main.kt new file mode 100644 index 0000000..400f8c9 --- /dev/null +++ b/advent-of-code/2023/day_04/src/main/kotlin/Main.kt @@ -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") +} diff --git a/advent-of-code/2023/kotlin/src/main/resources/day-04/input.txt b/advent-of-code/2023/day_04/src/main/resources/input.txt similarity index 100% rename from advent-of-code/2023/kotlin/src/main/resources/day-04/input.txt rename to advent-of-code/2023/day_04/src/main/resources/input.txt diff --git a/advent-of-code/2023/day_04/src/test/kotlin/CardsTest.kt b/advent-of-code/2023/day_04/src/test/kotlin/CardsTest.kt new file mode 100644 index 0000000..f567389 --- /dev/null +++ b/advent-of-code/2023/day_04/src/test/kotlin/CardsTest.kt @@ -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)) + } +} diff --git a/advent-of-code/2023/kotlin/src/main/kotlin/MyMath.kt b/advent-of-code/2023/kotlin/src/main/kotlin/MyMath.kt deleted file mode 100644 index 787e840..0000000 --- a/advent-of-code/2023/kotlin/src/main/kotlin/MyMath.kt +++ /dev/null @@ -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 -} diff --git a/advent-of-code/2023/kotlin/src/main/kotlin/Solution04.kt b/advent-of-code/2023/kotlin/src/main/kotlin/Solution04.kt deleted file mode 100644 index 608fc4b..0000000 --- a/advent-of-code/2023/kotlin/src/main/kotlin/Solution04.kt +++ /dev/null @@ -1,74 +0,0 @@ -class Solution04 { - /** Part 1. */ - fun getPointCount(lines: List): Int { - val cards = parseCards(lines) - return cards - .map{it.getPoints()} - .reduce{acc, points -> acc + points} - } - - /** Part 2. */ - fun getCardCount(lines: List): 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): MutableList { - val cards = mutableListOf() - - 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 { - return s - .split(" ") - .filter{it.isNotEmpty()} - .map{it.toInt()} - .toMutableList() - } -} - -data class Card( - val winningNumbers: MutableList = mutableListOf(), - val selectedNumbers: MutableList = 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) - } -} diff --git a/advent-of-code/2023/kotlin/src/main/resources/day-04/test.txt b/advent-of-code/2023/kotlin/src/main/resources/day-04/test.txt deleted file mode 100644 index 9bdb874..0000000 --- a/advent-of-code/2023/kotlin/src/main/resources/day-04/test.txt +++ /dev/null @@ -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 diff --git a/advent-of-code/2023/kotlin/src/test/kotlin/Solution04Test.kt b/advent-of-code/2023/kotlin/src/test/kotlin/Solution04Test.kt deleted file mode 100644 index 2e858e1..0000000 --- a/advent-of-code/2023/kotlin/src/test/kotlin/Solution04Test.kt +++ /dev/null @@ -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) - } -}