Advent of Code 2024 Day 3 - Odin

Advent of Code 2024 Day 3 - Odin
Photo by Ben Wicks / Unsplash

Previously I've done day1 and day2 in Zig. But day 3 rolled around, and it begs for regular expressions!

⚠️
Uh, spoilers, I guess.

Anyway, Zig doesn't have any regular expressions, and the library I found does not support captures. So instead of finding another library that does (or importing a C lib *shudders), I'm changing course and doing Day 3 in Odin!

Part 1

We're multiplying!

The input looks like a jumbled up set of instructions. We're interested in the following shape of data: mul(x,y), where x and y can be 1-3 digits. Anything else, we don't care.

So, regex: mul\((\d{1,3}),(\d{1,3})\) - basically, the letters m, u, l, followed by a literal open paren (, then a capture group in which there are 1-3 numbers (\d{1,3}), followed by a comma, then the same capture as before, and then a literal close paren ). In the code the backslashes are escaped, which is why you see 2 of each.

All this in Odin:

import "core:fmt"
import "core:os"
import "core:strconv"
import "core:strings"
import r "core:text/regex"

task1 :: proc(input: string) {
    regex, err := r.create("mul\\((\\d{1,3}),(\\d{1,3})\\)", flags = {.Global})
	if err != nil {
		fmt.printfln("failed creating regex: %s", err)
		os.exit(1)
	}

    total := calc_total(regex, string(input))
	fmt.println("task1:", total)
}

calc_total :: proc(regex: r.Regular_Expression, input: string) -> int {
	total := 0
	idx := 0
	for groups in r.match(regex, input[idx:]) {
		total += strconv.atoi(groups.groups[1]) * strconv.atoi(groups.groups[2])
		idx += groups.pos[0][1]
	}

	return total
}

.. that's it. If you know how to read Go, you pretty much know how to read Odin.

The only complication here is that Odin does not have a "regex match iterator" which gives you all the matches, only one that gives you the next one. So with a bit of string slicing, I'm iterating through the matches by slicing the string from the end of the last match.

Then a bit of conversion and we add them all up, all good.

Part 2

Do or do not, there is no try

Part two is the same as part one with the exception that there are instructions which enable/disable the mul instructions. Specifically, do() enables the ones after it, whereas don't() disables them.

We could write a more nuanced regular expression, or.. we could split the input. Basically, per the rules it starts as enabled, so the idea is this:

  1. split on do()
  2. in each of these blocks, split on don't()
  3. Take the first one of each block and calculate total from them

Same code as before, except the total calculation is this:

grand_total := 0
for do_block in strings.split_iterator(&input, "do()") {
    donts := strings.split(do_block, "don't()")
    grand_total += calc_total(regex, donts[0])
}

Onwards to day 4!