advent of code solutions aoc.oppi.li
haskell aoc

2025: add d5

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 086241a4 95dede5f

verified
+86 -9
+25 -9
book/filter.lua
··· 13 13 function Pandoc(doc) 14 14 local new_blocks = {} 15 15 local i = 1 16 - 16 + 17 17 while i <= #doc.blocks do 18 18 local current = doc.blocks[i] 19 - 19 + 20 20 if current.t == "Para" then 21 - -- Collect all consecutive Haskell code blocks following this paragraph 21 + -- collect all consecutive haskell code blocks following this paragraph 22 22 local code_blocks = {} 23 23 local j = i + 1 24 24 while j <= #doc.blocks and is_haskell(doc.blocks[j]) do 25 - table.insert(code_blocks, doc.blocks[j]) 25 + local code_block = doc.blocks[j] 26 + 27 + -- Ensure haskell-top blocks have haskell class for syntax highlighting 28 + local has_haskell_class = false 29 + for _, class in ipairs(code_block.classes) do 30 + if class == "haskell" then 31 + has_haskell_class = true 32 + break 33 + end 34 + end 35 + 36 + if not has_haskell_class then 37 + -- Add haskell class if not present 38 + table.insert(code_block.classes, 1, "haskell") 39 + end 40 + 41 + table.insert(code_blocks, code_block) 26 42 j = j + 1 27 43 end 28 - 44 + 29 45 if #code_blocks > 0 then 30 - -- Create a code div containing all the code blocks 46 + -- create a code div containing all the code blocks 31 47 local code_div = pandoc.Div(code_blocks, {class = "code"}) 32 48 local row = pandoc.Div({current, code_div}, {class = "row"}) 33 49 table.insert(new_blocks, row) 34 50 i = j -- skip past all the code blocks we just processed 35 51 else 36 - -- No code blocks following, create empty code div 52 + -- no code blocks following, create empty code div 37 53 local empty_div = pandoc.Div({}, {class = "code"}) 38 54 local row = pandoc.Div({current, empty_div}, {class = "row"}) 39 55 table.insert(new_blocks, row) 40 56 i = i + 1 41 57 end 42 58 else 43 - -- Not a paragraph, just pass through 59 + -- not a paragraph, just pass through 44 60 table.insert(new_blocks, current) 45 61 i = i + 1 46 62 end 47 63 end 48 - 64 + 49 65 return pandoc.Pandoc(new_blocks, doc.meta) 50 66 end
+61
src/2025/05.lhs
··· 1 + ## Day 5 2 + 3 + As always, we start by parsing the input into something more manageable. 4 + 5 + The input consists of two sections separated by a blank line. The first section contains fresh ingredient ID ranges (e.g., `3-5`), and the second contains available ingredient IDs. We split on `\n\n` to separate these sections, then parse each range into a list of two integers and each point as a single integer: 6 + 7 + ```haskell 8 + import Data.List.Split (splitOn) 9 + 10 + parse i = (ranges, points) 11 + where 12 + [h1, h2] = splitOn "\n\n" i 13 + ranges = map (map read . splitOn "-") $ lines h1 14 + points = map read $ lines h2 15 + ``` 16 + 17 + Our representation of intervals is list with two elements `[start, end]`. 18 + `contains` is a helper function to check if an ingredient ID falls within a given range: 19 + 20 + ```haskell 21 + contains :: Int -> [Int] -> Bool 22 + contains e [start, end] = e >= start && e <= end 23 + ``` 24 + 25 + ### Part 1 26 + 27 + For part one, we need to count how many of the available ingredient IDs are fresh. An ID is fresh if it falls within `any` of the fresh ranges: 28 + 29 + ```haskell 30 + p1 :: [[Int]] -> [Int] -> Int 31 + p1 is = length . filter (\p -> any (contains p) is) 32 + ``` 33 + 34 + ### Part 2 35 + 36 + For part two, we need to count the total number of unique ingredient IDs covered by all the fresh ranges. The methodology here is to iterate over the intervals sorted by ascending start bounds, while keeping track of the last end-bound we jumped over. This is necessary to avoid double-counting overlapping ranges. If we have `10-14` followed by `12-18`, we should avoid counting `12, 13, 14` twice. So we first account for `10-14`, and keep note of `14`. Upon encountering `12-18`, we then only count `18 - 14 = 4` new points, and not `18 - 12 + 1 = 7` new points, since the three points `12, 13, 14` are accounted for. 37 + 38 + ```haskell-top 39 + import Data.List (sortBy) 40 + import Data.Ord (comparing) 41 + ``` 42 + 43 + ```haskell 44 + p2 :: [[Int]] -> Int 45 + p2 = go 0 0 . sortBy (comparing (!! 0)) 46 + where 47 + go tot _ [] = tot 48 + go tot le (i@[start, end] : rest) 49 + | le > end = go tot le rest 50 + | contains le i = go (tot + end - le) end rest 51 + | le < start = go (tot + end - start + 1) end rest 52 + ``` 53 + 54 + Finally, a main function to wrap it all up: 55 + 56 + ```haskell 57 + main = do 58 + (is, pts) <- parse <$> getContents 59 + print $ p1 is pts 60 + print $ p2 is 61 + ```