DEV Community

Cover image for Advent of Code #2 (in JavaScript & Haskell)
Caleb Weeks
Caleb Weeks

Posted on

Advent of Code #2 (in JavaScript & Haskell)

Today's Advent of Code puzzle continues the theme of calculating a single value from a list of input, except this time, the input is text. Again, I solved the problem initially in Excel (where the hardest part was figuring out how to split a string by a delimiter...). Here is my attempt in Haskell and JavaScript.

Part One

Given a list of course instructions as seen below, we need to find the final destination of a submarine by adding up the horizontal and depth values and multiplying the two sums. A forward instruction adds horizontal position while up and down decrease and increase the depth, respectively.

course = ["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"]
Enter fullscreen mode Exit fullscreen mode

The first thing to do is parse out the numbers. I decided to use pattern matching to do this:

parseInstruction :: String -> (Int, Int)
parseInstruction ('f':'o':'r':'w':'a':'r':'d':x) = (read x, 0)
parseInstruction ('d':'o':'w':'n':x) = (0, read x)
parseInstruction ('u':'p':x) = (0, negate (read x))
parseInstruction _ = (0, 0)
Enter fullscreen mode Exit fullscreen mode

This will give us a tuple of horizontal and depth positions, so we just need to add them all up. Here is a helper function to add two tuples together:

sumTuples :: (Num a, Num b) => (a, b) -> (a, b) -> (a, b)
sumTuples (a1, b1) (a2, b2) = (a1 + a2, b1 + b2)
Enter fullscreen mode Exit fullscreen mode

After folding over the original course instructions with our tuple summing helper function following the instruction parser, we just multiply the final two values in the tuple together. A cool trick to do this is to uncurry the multiplication operator, which will simply pass both values of the tuple to the operator.

answer = uncurry (*) (foldl (
  \acc instruction -> sumTuples acc (parseInstruction instruction)
) (0, 0) course)
Enter fullscreen mode Exit fullscreen mode

This approach can be copied almost identically in JavaScript. A switch/case block is used instead of pattern matching for the parseInstruction function, and the final multiplication of the two values is chained in another reduce.

const parseInstruction = (instruction) => {
  const [direction, valueStr] = instruction.split(" ");
  const value = parseInt(valueStr);
  switch (direction) {
    case "forward":
      return [value, 0];
    case "down":
      return [0, value];
    case "up":
      return [0, -value];
  }
};

const sumTuples = ([a1, b1], [a2, b2]) => [a1 + a2, b1 + b2];

const answer = course
  .reduce(
    (acc, instruction) => sumTuples(acc, parseInstruction(instruction)),
    [0, 0]
  )
  .reduce((acc, x) => acc * x, 1);
Enter fullscreen mode Exit fullscreen mode

Part Two

The second part of the puzzle revises the meaning of the instructions such that up and down actually refer to the aim of the submarine, and the depth is actually calculated by multiplying the forward value by the current aim value. This requires keeping track of an additional accumulator value during the fold. The instruction parsing function stays the same, but we'll replace the sumTuples function with an accumulator function that takes care of the folding procedure:

accumulator :: (Int, Int, Int) -> String -> (Int, Int, Int)
accumulator (horizontal, aim, depth) instruction = 
  (\(h, a) -> (horizontal + h, aim + a, depth + (h * (aim + a)))) 
  (parseInstruction instruction)
Enter fullscreen mode Exit fullscreen mode

Horizontal and aim are accumulated as normal, but the depth is calculated as the current aim multiplied by the horizontal value from the instruction. We'll also need to manually pick out the depth and horizontal values from the triple to get the final product:

answer = (\(horizontal, aim, depth) -> horizontal * depth)
(foldl accumulator (0, 0, 0) course)
Enter fullscreen mode Exit fullscreen mode

The same changes can be made in JavaScript, but we'll also have to swap out the chained reduce hack for an intermediary variable assignment since we can't have inline lambdas. We could define a function and compose it with the reduce, but it wouldn't save much.

const accumulator = ([horizontal, aim, depth], instruction) => {
  const [h, a] = parseInstruction(instruction);
  return [horizontal + h, aim + a, depth + h * (aim + a)];
};

const [horizontal, aim, depth] = course.reduce(accumulator, [0, 0, 0]);

const answer = horizontal * depth;
Enter fullscreen mode Exit fullscreen mode

This problem had a lot of similarities to yesterday's problem, so fortunately, I didn't take quite as long coming up with these solutions. How would you implement a solution to these problems in Haskell or JavaScript? I'm particularly interested in better alternatives to the pattern matching hack for parsing the instructions in Haskell.

Top comments (1)

Collapse
 
koenvg profile image
koenvg

Nice solve, I tried something other than the pattern matching hack.
I am learning Haskell with Advent of code so there will be better solutions out there.


data Direction = Forward | Down | Up deriving (Read, Show)

type Command = (Direction, Int)

type Position = (Int, Int, Int)

readCommand :: String -> Command
readCommand command = ((read . capitalized) c :: Direction, read amount :: Int)
  where
    [c, amount] = words command

move :: Position -> Command -> Position
move (x, y, aim) (Down, a) = (x, y, aim + a)
move (x, y, aim) (Up, a) = (x, y, aim - a)
move (x, y, aim) (Forward, a) = (x + a, y + aim * a, aim)

part2 input = x * y
  where
    commands = map readCommand input
    (x, y, aim) = foldl move (0, 0, 0) commands
Enter fullscreen mode Exit fullscreen mode