Advent of Code 2024 Day 3 - Odin
Previously I've done day1 and day2 in Zig. But day 3 rolled around, and it begs for regular expressions!
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:
- split on
do()
- in each of these blocks, split on
don't()
- 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!