# /wakamoleguy

## Advent of Code 2022 - Day 1: Calorie Counting

It's that time of year again! We have 25 days to collect 50 stars and save Christmas. That's right: it's Advent of Code. This year I am solving the puzzles using Haskell, and I plan to write about each day's solution here.

Day 1: Calorie Counting is a bit of a warm-up, as usual for the first day's challenge. This year, we have input like this:

``````1000
2000
3000

4000

5000
6000

7000
8000
9000

10000``````

Each grouping represents the snacks carried by a single elf, and each line is the calorie count for an individual snack. In Part 1, we need to sum up each group and find the greatest. In Part 2, we need to find the three (3) greatest.

### First Solution

Here's my first attempt at solving the problem.

``````import Data.List
import Data.List.Split
import Input
import Test.Hspec

input :: IO String

spec :: IO ()
spec = hspec \$ do
describe "Day 01" \$ do
describe "Part 01" \$ do
it "runs on custom input" \$ do
myInput <- input
part1 myInput `shouldBe` 0 -- redacted
describe "Part 02" \$ do
it "runs on custom input" \$ do
myInput <- input
part2 myInput `shouldBe` 0 -- redacted

part1 :: String -> Int
part1 = maximum . calorieCounter

part2 :: String -> Int
part2 = sum . take 3 . reverse . sort . calorieCounter

calorieCounter :: String -> [Int]
calorieCounter = fmap (sum . fmap read . lines) . splitOn "\n\n"``````

I use Hspec to run my code as a test suite. Once I find the correct answer, I add it as an expectation. This allows me to refactor my code more easily, either in solving Part 2 or in cleanup afterwards. `Input.readDay` is a simple helper method that reads my input file from a local directory.

### Polishing

There were a few things I wanted to improve with this solution:

1. Part 1 and Part 2 can both be viewed as "Take the N greatest elves and sum them". But the code for Part 1 and Part 2 were pretty different. I wanted to move the sorting into the `calorieCounter`, so that it could be used in both parts.
2. `reverse` forces the full list to be evaluated, so `reverse . sort` sorts the whole list even when we only need a few top items. The Data.Ord module provides a `Down` type which can sort in descending order. Combined with `sortOn`, this allows us to lazily sort only as much of the list as we need to take.
3. Is there any way to simplify the `fmap`? `splitOn` can operate on any list, not just Strings (aka, `[Char]`). We should be able to use `lines` first, and then split the result into groups, rather than split into groups first, and then `fmap lines`.

Instead of `splitOn "\n\n"`, we would use `splitOn [""]`. Since each item in the list is a line, our delimiter is now one item long. We can replace `splitOn [""]` with `splitWhen null`.

The final code looks like this:

``````import Data.List
import Data.List.Split

part1 :: String -> Int

part2 :: String -> Int
part2 = sum . take 3 . calorieCounter

calorieCounter :: String -> [Int]
calorieCounter = sortOn Down . fmap (sum . fmap read) . splitWhen null . lines``````

#### Polish I didn't do:

I'm still using `head` in `part1` instead of `sum . take 1`. It's not quite equivalent! While `head` is more concise, it could bottom on an empty input.

`sum . take 1` will always return an Int, defaulting to 0 for an empty list. With this option, we could add the number of items to take as an argument to `calorieCounter`: `part1 = calorieCounter 1` and `part2 = calorieCounter 3`

A third option is `listToMaybe`, which would return `Just 69836` for my input and `None` for an empty input.

### Advent of Code 2022 Series

This post is part of a series describing my Haskell solutions to Advent of Code 2022.

Cheers!