John Biesnecker

AoC 2021 Day Two: Dive!

Piloting our submarine.

Advent of Code Day Two is upon us. This is another easy problem, as the first few tend to be (to lull you into a false sense of security, no doubt), so let’s dive (heh!) right in.

The Problem

See the original problem and my full solution.

Part One

You’re given a list of commands that consist of a command (either the string “forward”, “up”, or “down”) and an integer argument. Forward indicates that you move forward, up and down indicate that you dive or rise (you’re piloting a submarine, after all). At the end, what is the product of your horizontal and vertical positions?

Though it didn’t end up mattering here, I’ve learned the hard way from past AoC’s that you should always parse your input into structs rather trying to solve directly from the string versions, so let’s do that first.

type inputDayTwo struct {
    command string
    arg     int
}

func readInputDayTwo(fp *bufio.Reader) (res []inputDayTwo) {
    utils.ReadStrings(fp, func(s string) {
        var c string
        var a int
        fmt.Sscanf(s, "%s %d", &c, &a)
        res = append(res, inputDayTwo{c, a})
    })
    return
}

Now it’s a matter of tracking two pieces of state–the horizontal and vertical positions–as you’re traversing the list of commands.

func DayTwoA(fp *bufio.Reader) string {
    input := readInputDayTwo(fp)
    depth := 0
    pos := 0
    for _, c := range input {
        switch c.command {
        case "forward":
            pos += c.arg
        case "down":
            depth += c.arg
        case "up":
            depth -= c.arg
        }
    }
    return strconv.Itoa(depth * pos)
}

Part Two

Almost the same as part one, but with an added twist–instead of up and down directly modifying your depth, it instead modifies a third piece of state, aim, which is then used to calculate the depth change when moving forward.

Really this part isn’t any harder than the first, it’s just a matter of changing how the state is updated.

func DayTwoB(fp *bufio.Reader) string {
    input := readInputDayTwo(fp)
    depth := 0
    pos := 0
    aim := 0
    for _, c := range input {
        switch c.command {
        case "forward":
            pos += c.arg
            depth += aim * c.arg
        case "down":
            aim += c.arg
        case "up":
            aim -= c.arg
        }
    }
    return strconv.Itoa(depth * pos)
}

Improvements

If I were writing something like this for real, I would have defined an interface for the command handler:

type commandHandler interface {
    handleForward(arg int)
    handleUp(arg int)
    handleDown(arg int)
    getAnswer() int
}

and then two structs that implemented that interface. This would let you encapsulate the state nicely, and not repeat the instruction dispatch code. But for an AoC solution that is almost certainly overkill.