Skip to Content

Advent of Code 2023 - Day 4, in Kotlin - Scratchcards

Kotlin solutions to parts 1 and 2 of Advent of Code 2023, Day 4: 'Scratchcards'

Posted on

Today’s puzzle is a good lesson in not getting too attached to the way data is show in the description. There is a strong temptation to store everything. And in the real world, that may actually be the right answer. But for a puzzle whose solution we really only need once, we can avoid a lot of complexity by discarding almost everything once we’ve calculated what we need.

If you’d rather just view the code, my GitHub Repository is here.

Puzzle Input

Because each scratchcard is on its own row, we can take our input as a List<String>, just like in past days. We won’t declare input as a property because we’ll use it in the initializer to get what we need.

class Day04(input: List<String>) {

    private val cardMatches = input.map { parseCard(it) }

    private fun parseCard(input: String): Int {
        val winningNumbers = input.substringAfter(":")
                                  .substringBefore("|")
                                  .split(" ")
                                  .filter { it.isNotEmpty() }
                                  .toSet()

        val ourNumbers = input.substringAfter("|")
                              .split(" ")
                              .filter { it.isNotEmpty() }
                              .toSet()

        return winningNumbers.intersect(ourNumbers).size
    }
}

The fun part about this puzzle is that we don’t care about most of the data once we’ve got our score figured out. We don’t care what numbers are on the card or what numbers were picked once we know how many we match. And we certainly don’t care about the card’s id so long as we can keep them in order. Given that we will only store the score for each card.

We will use substringAfter and substringBefore to pick through the input, and grab what we need. We don’t bother converting anything to integers because Strings will compare the same way, so why waste the effort?

We make two sets - one for the winningNumbrs and one for ourNumbers. The only part that we store, the score, is calculated by measuring how many elements these two sets have in common via the intersect function.

⭐ Day 4, Part 1

The puzzle text can be found here.

The only thing left to do for part 1 is to count up the scores, using the formula provided. Because Kotlin doesn’t have a built in function to raise an Int to an Int power, we will have to call the existing pow function off of a Double (2.0).

// In Day04

fun solvePart1(): Int =
    cardMatches.sumOf { 2.0.pow(it-1).toInt() }

Once again, we use the ever-helpful sumOf function to sum up the scores of all our cardMatches. We do this by raising 2 to the score-1’s power and casting the result back to an Int.

Star earned! Onward!

⭐ Day 4, Part 2

The puzzle text can be found here.

For part 2 our data is in a usable format so our calculation is rather straight forward.

// In Day04

fun solvePart2(): Int {
    val cards = IntArray(cardMatches.size) { 1 }
    cardMatches.forEachIndexed { index, score ->
        repeat(score) {
            cards[index+it+1] += cards[index]
        }
    }
    return cards.sum()
}

We will declare an IntArray to represent how many cards of each id we have. We seed this array with 1 in each spot because we have one of each card.

Next, we look through our cardMatches list which contains the scores for each card. Using forEachIndexed lets us know the place within the cardMatches array as well as the value of the array (the score). We use these facts to calculate how many cards we just won.

Finally, we repeat a calculation score number of times, which allows us to calculate how many cards we need to add to the next score number piles of cards. I think there’s probably a way I could have used some version of fold or reduce here, but this seems clearer to me. In the real world, we would do some bounds checking here to make sure we don’t attempt to write to an element in cards that doesn’t exist, but the puzzle explicitly states this cannot happen, so I’m not going to write code to handle that.

Star earned! See you tomorrow!

Further Reading

  1. Index of All Solutions - All posts and solutions for 2023, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 4
  4. Advent of Code - Come join in and do these challenges yourself!