Skip to Content

Advent of Code 2024 - Day 3, in Kotlin - Mull It Over

Kotlin solutions to parts 1 and 2 of Advent of Code 2024, Day 3: 'Mull It Over'

Posted on

Today we’ll solve the puzzle with the help of a pair of regular expressions. I don’t use regular expressions very often in my day-to-day work, but very early in my career I wrote a lot of regular expressions in PERL and retained enough of it to solve today with a minimum of aggravation.

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

Puzzle Input

It should be noted that today’s input file is spread over multiple lines that must be concatenated. I do this from my test case via a helper function that I have used for the past several Advents of Code.

class Day03(private val input: String) {

}

We will take this String in as input and keep it as a private val in our daily class. Parsing will take place later.

⭐ Day 3, Part 1

The puzzle text can be found here.

For part 1, we’ll use a Regular Expression to pick through our input and find all of the mul instructions. One of the features of regular expressions is capture groups. Meaning “match this string, and capture the values inside it”. So we’ll create a regular expression to find mul instructions, and capture the two values being multiplied.

// In Day03

private fun executeMuls(instructions: String): Int =
    """mul\((\d{1,3}),(\d{1,3})\)"""
        .toRegex()
        .findAll(instructions)
        .sumOf { match ->
            match.groupValues
                .drop(1)
                .map { it.toInt() }
                .reduce(Int::times)
        }

I know that’s a lot, so let’s go through it.

First, we create a regular expression that finds mul and groups the arguments. Next we turn this String into a Regex via toRegex(). We then findAll of the places the regex matches in the instructions. We can then get the sumOf the mul values.

To do this, we look a the match groupValues for each match (each “mul(a,b)"). Since groupValues has the entire match and not just the group values as the first element, we drop it. Then we can map each of the resulting group String to an Int, and then reduce to multiply them all together.

At the end of this function call, we will have found every mul instruction, multiplied its arguments together, and added them up.

Calling this function with our input solves part 1.

// In Day03

fun solvePart1(): Int =
    executeMuls(input)

Star earned! Onward!

⭐ Day 3, Part 2

The puzzle text can be found here.

Part 2 is also solved via a regular expression!

Instead of thinking of don't()..<bad stuff here>..do(), let’s think about it backwards: do()..<good stuff here>..don't(). Anything between do() and don't() is something we care about. Anything else is not. We have to take care and watch out for a couple of edge cases. For example, there is an implicit do() a the start of the instructions (represented by ^ in our regex), and there is an implicit don't() at the end of the instructions (represented by $ in the regex).

// In Day03

private fun executeDisabled(instructions: String): Int =
    """(^|do\(\)).*?($|don't\(\))"""
        .toRegex()
        .findAll(instructions)
        .sumOf { executeMuls(it.value) }

One thing to notice about this regular expression is that the .*? in the middle is reluctant. In regular expressions there are two kinds of matches: Greedy (“give me the largest possible match”) or reluctant (“give me the shortest possible match”). Since we want to pick out anything between do() and next occurrance of don't(), we will make this a reluctant match. This is so we don’t overrun the first don't() we run into and match a later one (a greedy qualifier would do this if we wanted it).

The regular expression itself can be read as: Find either the start of a string or “'“do()"'”, any number of characters, and either the end of a string or “don’t”. Anything else is ignored.

We take all of these matches, and get the sumOf subsequent calls to our previously written executeMuls function.

Calling the executeDisabled function with our input solves part 2!

// In Day03

fun solvePart2(): Int =
    executeDisabled(input)

Star earned! See you tomorrow!

Further Reading

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