Day 9: Stream Processing
# 9 Dec 2017Notice: If you have coding experience, I highly recommend to try and solve the puzzle in a language you already know. It's a lot of fun and you'll be able to focus on Go more later on.
Hi there, welcome on day 9! If you haven’t done so yet, go and read the problem description. Go on, I’ll wait here until you’re ready.
Good, you’re back! Let’s get started. On the agenda for today: Regular Expressions!
Well, it had to happen one day. My solution for today involves regular expressions! Go ships with a well equiped package for regular expressions called regexp
. Regular expressions must be compiled by Go to be executed, using a function called (Must)Compile. The Compile
variant will return an error if the regular expression fails. For expressions hardcoded in your codebase, you may want to use MustCompile, which panics if it can’t compile the regular expression.
With that out of the way, we’ll need two regular expressions: one for the escaped charachters !.
and one for garbage <[^>]*>
. They’re compiled and assigned to package variables. Packages variables are declared on the package level and can be used by outside backage if their name starts with a capital letter. An outside package could access the regular exprssion for garbage like day09.GARBAGE
. In this case, we don’t really want it to be accessible from the outside, but rather prevent recompilation of the regular expressions, as it’s an expensive process. The initial declaration and assignment of package variables is executed just once when the package is first loaded, so it’s the ideal location to compile the regular expressions.
We have our regular expressions and our file as a byte slice. We could convert our byte slice to a string to match the regular expressions, but fortunately for us, the Go package is perferctly happy with a byte slice as well! All the functions follow the same pattern: BaseName[String][SubName]
. For FindAll
finds all occurrences of a pattern in byte slice. FindAllString
finds all occurrences in a string. FindAllSubmatch
and FindAllStringSubmatch
find all matches and their submatches in a byte slice and string respectively.
The general algorithm then goes like this:
- Remove all escaped charachters using
IGNORABLE.ReplaceAll
with an empty byte slice as replacement value. - Find all matches for
GARBAGE
usingFindAll
and sum the lengths to find the anwser for part B. - Remove all garbage using
GARBAGE.ReplaceAll
- Loop over the resulting byte slice, counting the number of groups to get the solution for part A.
package day09
import (
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
)
var IGNORABLE = regexp.MustCompile(`!.`)
var GARBAGE = regexp.MustCompile(`<[^>]*>`)
func Solve() {
bs, err := ioutil.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
// Remove all ignored characters from the stream
bs = IGNORABLE.ReplaceAll(bs, []byte{})
// Find all the garbage segments and count their length
gbsize := 0
gb := GARBAGE.FindAll(bs, -1)
for _, g := range gb {
gbsize += len(g) - 2
}
fmt.Println("Part B", gbsize)
// Remove all the garbage from the stream
bs = GARBAGE.ReplaceAll(bs, []byte{})
// Keep track of the depth of the current group.
// Add the depth to the sum each time we go deeper.
sum := 0
group := 0
for _, b := range bs {
switch b {
case '{':
group++
sum += group
case '}':
group--
}
}
fmt.Println("Part A", sum)
}
That’s it! If you ever need regular expressions in your code, make sure to read up on the package documentation. It’s quite extensive and helpful. As usual, my full solution is available in my github repository. See you all tomorrow!