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"]
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)
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)
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)
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);
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)
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)
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;
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)
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.