Day 6

This day is largely string munging, which is quite boring, but also quite easy to do with Haskell!

Part 1

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 $ rest

Thus, 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 init

By 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 words

This 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"]]

Part 2

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