This day is largely string munging, which is quite boring, but also quite easy to do with Haskell!
This part needs us to consider each operation on a column.
Data.List.transpose fortunately makes this very
simple. If we are able to massage our input into the form
["+", "1", "2", "3"], we can use this simple
solve function to produce the result. The only
motivation to use this format is the ease of pattern-matching over
the start of the list.
solve :: [String] -> Int
solve ("+" : rest) = sum . map read $ rest
solve ("*" : rest) = product . map read $ restThus, to solve the first part, we can break up our input using
lines. rot is a simple function that
rotates a list forwards to move the last element to the first.
rot :: [a] -> [a]
rot = liftA2 (:) last initBy doing rot . lines upon our input, we have a
list of the form:
["+ *", "1 2 3", "4 5 6"]
Next, we define an align function to line up the
columns, and define our solution to part 1 like so:
import Data.List (transpose)p1 :: String -> Int
p1 = sum . map solve . align . rot . lines
where
align = transpose . map wordsThis method simply breaks each item in the list by spaces, and then transposes them, so we end up with:
[["+", "1", "2", "3"], ["*", "4", "5", "6"]]
We are now tasked with lining up the digits in a columnar fashion to form a number:
123 ...
45 ...
6 ...
* ...
Here, the numbers to operate on are 356, 24 and 1.
Unfortunately, we can no longer employ words as it
breaks on all whitespace. We must now transpose our list as-is, to
make sense of the digits. Ignore the last row for a second, and
only consider the first 3 rows:
123
45
6
Upon transposing this, we get:
1
24
356
This is exactly what we need!
Let us start with a similar base, using lines to
break things up, and rot to move the line of
operators to the start. Our align function can now be
defined as below.
import Data.List.Split (splitWhen)p2 :: String -> Int
p2 = sum . map solve . align . rot . lines
where
align =
zipWith (:)
<$> (words . head)
<*> (splitWhen (all (' ' ==)) . transpose . tail)We first start with zipWith (:) to join the
operation "+" with the rest of the numbers to
produce:
["+", "1", "2", ...]
Remember that this is the format solve
accepts!
We are using <*> to perform “sequential
application”. The first part …
words . head
… is used to split up the operator line into a list of operators.
The second part …
splitWhen (all (' ' ==)) . transpose . tail
…is used to transpose numbers! Note what happens when we transpose the lines containing numbers, we get:
["1 ","24 ","356"," ","369","248","8 ", ...]
There is an element containing all spaces (which was present
between each column), Data.List.Split.splitWhen is
employed to detect and chunk our list into runs of valid
numbers.
Finally, a main function to wrap it all up:
main = do
n <- getContents
print $ p1 n
print $ p2 n