Advent of Code #2 (in JavaScript & Haskell)
source link: https://dev.to/sethcalebweeks/advent-of-code-2-in-javascript-haskell-2nea
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK