AoC 2025 Day 3
Part 1
But First, Dessert
I looked at the input data and I’m going to need to eat my parsing peas this time. But first, we are going to solve the algorithm part.
The input data consists of a series of integers. We need to select the two values that when combined in left to right order will generate the largest number. For example, if the series of digits was 424584136 then the 8 and then the 6 would form 86.
The puzzle answer is the sum of the constructed number from all of the lines.
I’m going to assume the data will be in a list of digit values as integers. For the example above we have [4,2,4,5,8,4,1,3,6].
This seems really straight forward. Maybe I should re-read the puzzle. (o;
Here are my thoughts:
- Find the maximum value in the list (less the last value).
- Split the list at the first occurrence of the maximum value.
- Find the maximum in the tail portion of the split.
lineValue = (firstMax * 10) + secondMax- Do this for each of the lines and add up the results
In code:
import Data.Char
getFirstValue :: [Int] -> Int
getFirstValue = maximum . init
getSecondValue :: Int -> [Int] -> Int
getSecondValue v xs = maximum (tail (dropWhile (/=v) xs))
maxJolt :: [Int] -> Int
maxJolt xs =
(maxValue * 10) + (getSecondValue maxValue xs)
where maxValue = getFirstValue xs
This seems to work, but it seems like there should be a nicer way to write getSecondValue. If we add a few more ((())) it could be Lisp.
Maybe using function application operator looks nicer?
getSecondValue :: Int -> [Int] -> Int
getSecondValue v xs = maximum $ tail $ dropWhile (/=v) xs
If we can get the input data into a list of lists then the puzzle answer will be:
totalJoltage :: [[Int]] -> Int
totalJoltage xs = sum $ map maxJolt xs
Vegetables
Well, it is time to figure out how to parse this stuff. I hope that I can just do something like, read the file into a [[Int]] and then use totalJoltage. I expect that there will be some dance because the signature is going to be something like IO [[Int]] … so perhaps we use fmap? I really don’t know.
Step 1, read the data into the desired format
Step 2, figure out how to apply the function without needing to use a do statement
I recommend reading “Learn You A Haskell For Great Good”. I read the first 6 chapters before starting Day 1. As I was thinking about how to convert a Char -> Int and eating pie, I was reading through chapter 7 and look at that! Data.Char has a function called digitToInt that does exactly that!
Ok… this is what I’ve got. I can’t figure out a “nice” way to deal with the IO Monad still. Here we sit.
day3_part1 = do
input_data <- lines <$> readFile "day3_input.txt"
let inputData = map (map digitToInt) input_data
let joltage = totalJoltage inputData
return joltage
As often is the case… I wrote my message of defeat above and then tried something else. This also works and might make me happier:
day3_input = do
input_data <- lines <$> readFile "day3_input.txt"
let inputData = map (map digitToInt) input_data
return inputData
day3_part1 :: IO Int
day3_part1 = fmap totalJoltage day3_input
Part 2
The second half of the puzzle matches the rules of the first but now we need 12 digits. Previously I was shifting the search window by 1 so there would be enough digits for the last digit. Same sliding but for each iteration should do the trick. Unfortunately, I didn’t make Part 1 generic.
The technique I used before was to:
- find the maximum value in the list
dropWhileon the maximum value to shrink the list to what remains
I want to keep around both the maximum value and the unused part of the list. getNextDigit will return both in tuple.
getNextDigit :: [Int] -> (Int, [Int])
getNextDigit xs =
(m, tail $ dropWhile (/=m) xs)
where m = maximum xs
This building block can now be used to get a sequence of digits representing the new jolt value. We just need to control the sliding window for each digit. We need to reserve the number of digits at the end that we still need to add.
To illustrate, if we are making a 5 digit number from this sequence:
[4,6,2,7,9,4,2,9]
after getting the first digit we have
- sequence:
[9] - remaining:
[4,2,9]
As you can see, we already don’t have enough digits to finish the sequence.
We want to reserve digits and then append one at a time as we get the next digit.
So this sequence [4,6,7,2,9,4,2,9] gets divided into:
- current:
[4,6,7,2] - reserve:
[9,4,2,9]
after getting the first digit we append and then have:
- sequence:
[7] - current:
[2,9] - reserve:
[4,2,9]
The function joltSeq receives the current and reserve lists and produces the appropriate sequence using recursion. The recursion is terminated if either the current or reserve list runs out of values to process.
joltSeq :: [Int] -> [Int] -> [Int]
joltSeq [] _ = []
joltSeq xs [] = [maximum xs]
joltSeq xs reserve =
(digit : joltSeq (t ++ [head reserve]) (tail reserve))
where (digit,t) = getNextDigit xs
Given some properly formed lists, we now get a list of digits out that represent our jolt value. Converting that to an integer is just a simple fold that multiplies the accumulated value by 10 and adds the new value.
seqToInt :: [Int] -> Int
seqToInt = foldl (\acc x -> acc * 10 + x) 0
Our new maxJolt' will take the number of digits to use and the list of integers. Map that across our list of lists, sum the values and we have the solution for Part 2.
maxJolt' :: Int -> [Int] -> Int
maxJolt' digitCnt xs =
seqToInt $ joltSeq digits reserve
where (digits,reserve) = splitAt (length xs - (digitCnt-1)) xs
totalJoltage' :: [[Int]] -> Int
totalJoltage' xs = sum $ map (maxJolt' 12) xs
day3_part2 :: IO Int
day3_part2 = fmap totalJoltage' day3_input
Thoughts
There are several parts of my solution that I’m not happy with. I suspect there are “better” ways to accomplish these things in Haskell and part of the fun will be revisiting the exercises when I’m done to see what I have learned.
Haskell is really great! Day 3 in the books and I’m loving it.