···1010 return false
1111end
12121313+function is_wrappable(block)
1414+ -- Block types that should be wrapped in the two-column layout
1515+ local wrappable_types = {
1616+ Para = true,
1717+ BulletList = true
1818+ }
1919+ return wrappable_types[block.t] or false
2020+end
2121+1322function Pandoc(doc)
1423 local new_blocks = {}
1524 local i = 1
1616-2525+1726 while i <= #doc.blocks do
1827 local current = doc.blocks[i]
19282020- if current.t == "Para" then
2121- -- collect all consecutive haskell code blocks following this paragraph
2929+ if is_wrappable(current) then
3030+ -- collect all consecutive haskell code blocks following this element
2231 local code_blocks = {}
2332 local j = i + 1
3333+2434 while j <= #doc.blocks and is_haskell(doc.blocks[j]) do
2535 local code_block = doc.blocks[j]
2636···5666 i = i + 1
5767 end
5868 else
5959- -- not a paragraph, just pass through
6969+ -- not a wrappable element, just pass through
6070 table.insert(new_blocks, current)
6171 i = i + 1
6272 end
6373 end
6464-7474+6575 return pandoc.Pandoc(new_blocks, doc.meta)
6676end
···66{-# LANGUAGE LambdaCase #-}
7788import qualified Data.Map as M
99-import System.Environment (getArgs)
1091110grid = M.fromList $ zip [(x, y) | y <- [1, 0, -1], x <- [-1, 0, 1]] [1 .. 9]
1211···3635p2 ns = tail $ map (grid2 M.!) $ scanl (foldl' (mmove grid2)) (-2, 0) ns
37363837main = do
3939- args <- getArgs
4040- n <- case args of
4141- ["-"] -> getContents
4242- [file] -> readFile file
4343- let f = lines n
4444- print $ p1 f
4545- print $ p2 f
3838+ n <- lines <$> getContents
3939+ print $ p1 n
4040+ print $ p2 n
4641```
-2
src/2025/02.lhs
···11-# 2025
22-31## Day 2
4253Starting with the parse step, the input is of the form `11-22,15-17,...`.
+84
src/2025/10.lhs
···11+## Day 10
22+33+Start by parsing the input:
44+55+```haskell
66+import Data.List.Split (splitOn)
77+88+parse :: String -> [(Int, [[Int]], [Int])]
99+parse = map (line . words) . lines
1010+ where
1111+ line (t : ws) =
1212+ (target t, map list (init ws), list (last ws))
1313+ target s = fromBits . init $ tail s
1414+ list = map read . splitOn "," . init . tail
1515+```
1616+1717+Of interest is the lighting sequence, which is represented as dots (`.`) and hashes (`#`). You can think of this as a number expressed as zeroes and ones:
1818+1919+```haskell
2020+fromBits :: [Char] -> Int
2121+fromBits = foldl f 0 . reverse
2222+ where
2323+ f acc '.' = acc * 2
2424+ f acc '#' = acc * 2 + 1
2525+```
2626+2727+And so we move onto the first part.
2828+2929+### Part 1
3030+3131+The first part requires us to find the combinations of buttons that can result in the lighting sequence, with repetitions allowed. Pressing once switches on the lights, and pressing a second time switches it off. This behavior is identical to XOR! Therefore, the first part is a combination-sum problem, but using XOR, so a combination-xor problem if you will.
3232+3333+First, we'd need to convert a button into its bitwise form:
3434+3535+```haskell-top
3636+import Data.Bits (bit, xor, (.|.))
3737+import Control.Monad (filterM)
3838+```
3939+4040+```haskell
4141+toBits :: [Int] -> Int
4242+toBits = foldl1 (.|.) . map bit
4343+```
4444+4545+Next, we require the powerset of all possible buttons:
4646+4747+```haskell
4848+powerset [] = [[]]
4949+powerset (x : xs) = map (x :) subset ++ subset
5050+ where
5151+ subset = powerset xs
5252+```
5353+5454+Thus, our combination-xor is defined like so:
5555+5656+```haskell
5757+comboXor target =
5858+ filter ((== target) . foldl xor 0) . powerset
5959+```
6060+6161+And the solution to the first part, is simply the application of `comboXor` to each line of the input, and finding the smallest possible sequence in each solution:
6262+6363+```haskell
6464+p1 = sum . map solveLine
6565+ where
6666+ solveLine (target, btns, _) =
6767+ minimum . map length $ comboXor target (map toBits btns)
6868+```
6969+7070+### Part 2
7171+7272+This part requires us to minimize a collection of equations to identify N coefficients. The solution for this part is given quite easily by LP/SAT solvers, but not so easily by hand. Some resources:
7373+7474+- [Simplex Algorithm](https://en.wikipedia.org/wiki/Simplex_algorithm)
7575+- [Gaussian Eliminiation](https://en.wikipedia.org/wiki/Gaussian_elimination)
7676+7777+Finally, a main function to wrap it all up:
7878+7979+```haskell
8080+main = do
8181+ n <- parse <$> getContents
8282+ print $ p1 n
8383+```
8484+
+130
src/2025/11.lhs
···11+## Day 11
22+33+Start by munging the input:
44+55+```haskell
66+parse = graph . map (l . words . filter (/= ':')) . lines
77+ where
88+ l (node : edges) = (node, edges)
99+```
1010+1111+Our graph representation is a map of nodes to the list of outgoing edges:
1212+1313+```haskell-top
1414+import qualified Data.Map as M
1515+```
1616+1717+```haskell
1818+graph ls =
1919+ M.union
2020+ (M.fromList ls)
2121+ (M.fromList [(e, []) | (_, es) <- ls, e <- es])
2222+```
2323+2424+This, the edge list (a list of `(node, edge)`) is given by:
2525+2626+```haskell
2727+edges = concatMap (\(n, e) -> map (n,) e) . M.toList
2828+```
2929+3030+And the list of nodes is simply:
3131+3232+```haskell
3333+nodes = M.keys
3434+```
3535+3636+The list of neighbours of a node:
3737+3838+```haskell
3939+neighbours = (M.!)
4040+```
4141+4242+That forms our primitive graph library, onto the solution itself!
4343+4444+### Part 1
4545+4646+The first part requires us to find all paths from one node in the digraph to another node. An algorithm for this is to traverse the graph in topological order, and the number of "ways" into each node would be them sum of ways into all neighbours.
4747+4848+To first sort the nodes in topo-order however, we need to know the indegrees of all nodes. Every destination node in the edge-list has an indegree of 1, so sum those up, and then combine with all nodes. The second step is necessary to include those nodes aren't present in the edge-list already. Recall that `M.union` is left-biased, so we won't be overwriting calculations.
4949+5050+```haskell
5151+indeg :: (Ord a) => M.Map a [a] -> M.Map a Int
5252+indeg =
5353+ M.union
5454+ <$> M.fromListWith (+) . map ((,1) . snd) . edges
5555+ <*> M.fromList . map (,0) . nodes
5656+```
5757+5858+Thus, our toposort algorithm is given by:
5959+6060+```haskell
6161+toposort m
6262+ | M.size m == 0 = []
6363+ | otherwise = start ++ toposort rest
6464+ where
6565+ start = nodes . M.filter (== 0) $ indeg m
6666+ rest = foldr M.delete m start
6767+```
6868+6969+Now, to calculate the number of paths, given a graph, a start node and an end node, we define `paths` like so:
7070+7171+```haskell
7272+paths g src dst = foldl' go start (toposort g) M.! dst
7373+ where
7474+ start =
7575+ M.insert src 1
7676+ . M.fromList
7777+ . map (,0)
7878+ . nodes
7979+ $ g
8080+8181+ go ways node =
8282+ M.unionWith (+) ways (upd ways node)
8383+8484+ upd ways node =
8585+ M.fromList
8686+ . map (,ways M.! node)
8787+ $ neighbours g node
8888+```
8989+9090+To explain a bit further
9191+9292+- `start`: this is the initial `Map` containing the number of paths from `start` to all other nodes in the graph. The number of paths from `start` to itself is 1
9393+- `go`: this is the fold function that runs on every node in topo-order. We simply update the `ways` map and combine with the existing map
9494+- `upd`: find the neighbours of this node, and update their entry in the `ways` Map to be equal to the number of ways to get to `node`
9595+9696+Thus, the solution to the first part is given as:
9797+9898+```haskell
9999+p1 n = paths n "you" "out"
100100+```
101101+102102+### Part 2
103103+104104+And the solution to the second part is given as:
105105+106106+```haskell
107107+p2 n =
108108+ product
109109+ [ paths n "svr" "fft",
110110+ paths n "fft" "dac",
111111+ paths n "dac" "out"
112112+ ]
113113+ +
114114+ product
115115+ [ paths n "svr" "dac",
116116+ paths n "dac" "fft",
117117+ paths n "fft" "out"
118118+ ]
119119+```
120120+121121+Because in a digraph, the number of paths from `A -> B` via `C`, is given by the product of paths from `A -> B` and `B -> C`.
122122+123123+Finally, a main function to wrap it all up:
124124+125125+```haskell
126126+main = do
127127+ n <- parse <$> getContents
128128+ print $ p1 n
129129+ print $ p2 n
130130+```
+75
src/2025/12.lhs
···11+## Day 12
22+33+This input looks quite complex, so lets start by defining some types to fit the input into:
44+55+```haskell
66+type Region = (Int, Int, [Int])
77+88+type Shape = M.Map (Int, Int) Char
99+area = M.size . M.filter (== '#')
1010+```
1111+1212+Onto parsing:
1313+1414+```haskell-top
1515+import Data.List.Split (splitOn)
1616+import qualified Data.Map as M
1717+```
1818+1919+We are using `<*>` here, which stands for sequential application. We are applying two functions onto the input, one to parse regions and one to parse shapes:
2020+2121+```haskell
2222+parse :: String -> ([Shape], [Region])
2323+parse = ((,) <$> sps <*> regs) . splitOn "\n\n"
2424+ where
2525+ regs = map parseReg . lines . last
2626+ sps = map parseShape . init
2727+```
2828+2929+Parsing a region is then simply:
3030+3131+```haskell
3232+parseReg :: String -> Region
3333+parseReg l = (x, y, map read idxs)
3434+ where
3535+ (dims : idxs) = words l
3636+ [x, y] = map read . splitOn "x" $ init dims
3737+```
3838+3939+And to parse a shape (we are using a map to represent a shape):
4040+4141+```haskell
4242+parseShape :: String -> Shape
4343+parseShape n =
4444+ M.fromList
4545+ [ ((x, y), c)
4646+ | (x, r) <- zip [0 ..] (tail (lines n)),
4747+ (y, c) <- zip [0 ..] r
4848+ ]
4949+```
5050+5151+Moving onto the solution itself.
5252+5353+### Part 1
5454+5555+At first glance, this problem seems quite difficult. There is not an optimal algorithm that I know of, and backtracking with rotations could take quite a while. So it would make sense to reduce the problem space as much as possible right away. One way to do that is to simply check if all shapes can fit within their respective regions. And turns out that is sufficient to solve this day's problem!
5656+5757+```haskell
5858+p1 (sps, regs) = length $ filter fits regs
5959+ where
6060+ fits (x, y, cs) =
6161+ (x * y) >= sum (zipWith (*) (map area sps) cs)
6262+```
6363+6464+Historically, the last day of AOC has always been quite simple.
6565+6666+6767+Finally, a main function to wrap it all up:
6868+6969+```haskell
7070+main = do
7171+ n <- parse <$> getContents
7272+ print $ p1 n
7373+```
7474+7575+See you next year!