Advent of Code 2021 day 2

    The main questions for today's task are how to represent what's important. There are two things: the commands given to the submarine, and the current position. I decided to go with clarity over cleverness.

    I could represent the position as a pair (or triple) of numbers. I could do something clever with complex numbers. I could use the Linear package to represent coordinates. I could use records. But in this case, some simple algebraic types are more than good enough.

    type Course = [Command]
    data Command =   Forward Int
                   | Up Int
                   | Down Int
                   deriving (Eq, Show)
    
    data Position = Position Int Int -- forward, depth
      deriving (Eq, Show)               
    
    data AimedPosition = AimedPosition Int Int Int -- forward, depth, aim
      deriving (Eq, Show)   
    

    This has the additional advantage from the "parse, don't validate" approach, in that I can't have an invalid Command.

    It's back to attoparsec for parsing, which remains remarkably clear.

    courseP = commandP `sepBy` endOfLine
    commandP = forwardP <|> upP <|> downP
    
    forwardP = Forward <$> ("forward " *> decimal)
    upP = Up <$> ("up " *> decimal)
    downP = Down <$> ("down " *> decimal)
    

    A course is a list of commands separated by newlines. A command is either forward, up, or down. Each of those is the text string followed by a number, and we don't bother recording the text string.

    Finding the final position requires writing down the rules as a function (using pattern matching for the different cases), then folding the commands into a final Position.

    followCourse :: Course -> Position
    followCourse = foldl courseStep (Position 0 0)
    
    courseStep :: Position -> Command -> Position
    courseStep (Position h d) (Forward n) = Position (h + n) d
    courseStep (Position h d) (Up n)      = Position h (d - n)
    courseStep (Position h d) (Down n)    = Position h (d + n)
    
    followAimedCourse :: Course -> AimedPosition
    followAimedCourse = foldl courseAimedStep (AimedPosition 0 0 0)
    
    courseAimedStep :: AimedPosition -> Command -> AimedPosition
    courseAimedStep (AimedPosition h d a) (Forward n) = AimedPosition (h + n) (d + a * n) a
    courseAimedStep (AimedPosition h d a) (Up n)      = AimedPosition h d (a - n)
    courseAimedStep (AimedPosition h d a) (Down n)    = AimedPosition h d (a + n)
    

    The only wrinkle is to remember to fold from the left, so commands are applied from first to last!

    Code

    You can get the code from my locally-hosed Git repo, or from Gitlab.

    Neil Smith

    Read more posts by this author.