Advent of Code 2025

Compare changes

Choose any two refs to compare.

-6
generate.py
···
-
# for i in range(1,25):
-
# f = open(f"src/Day{i}.hs", "w")
-
# f.write(f"module Day{i} where\n\npart1 :: String -> String\npart1 _ = \"Day {i} part 1\"\n\npart2 :: String -> String\npart2 _ = \"Day {i} part 2\"\n")
-
-
for i in range(1,13):
-
print(f"\"{i}\" -> do\n putStrLn $ \"Part 1: \" ++ Day{i}.part1 input\n putStrLn $ \"Part 2: \" ++ Day{i}.part2 input\n ",end='')
+119 -2
src/Day10.hs
···
module Day10 where
+
import Data.Ratio ((%), Ratio)
+
import Util (split)
+
import Data.Maybe (catMaybes)
+
import Data.Ord (comparing)
+
import Data.List (sortBy)
+
import qualified Data.Map as Map
+
import Data.Map (Map)
+
import Debug.Trace
+
+
type Button = [Int]
+
type Display = [Int]
+
type Machine = (Button, [Button], [Int])
+
+
parse :: String -> [Machine]
+
parse = map parseLine . lines
+
where parseDisplay :: String -> [Int]
+
parseDisplay s = [i | (i,c) <- zip [0..] (init (tail s)), c == '#']
+
+
parseInts :: String -> [Int]
+
parseInts s = map read $ split ',' $ (init (tail s))
+
+
parseLine line =
+
let w = words line
+
disp = head w
+
jolt = last w
+
in (parseDisplay disp, map parseInts (init (tail w)), parseInts jolt)
+
+
xor :: [Int] -> [Int] -> [Int]
+
xor a [] = a
+
xor [] b = b
+
xor (a:as) (b:bs) | a < b = (a : xor as (b:bs))
+
xor (a:as) (b:bs) | a == b = xor as bs
+
xor (a:as) (b:bs) | a > b = (b : xor (a:as) bs)
+
+
minimumMaybe :: [Maybe Int] -> Maybe Int
+
minimumMaybe ns =
+
case catMaybes ns of
+
[] -> Nothing
+
ns' -> Just $ minimum ns'
+
+
solveMachine :: Machine -> Maybe Int
+
solveMachine ([],[],_) = Just 0
+
solveMachine (_,[],_) = Nothing
+
solveMachine (goal, (b:bs), jolt) =
+
minimumMaybe [solveMachine (goal, bs, jolt),
+
(+ 1) <$> solveMachine (xor goal b, bs, jolt)]
+
part1 :: String -> String
-
part1 _ = "Day 10 part 1"
+
part1 input =
+
let machines = parse input
+
solutions = map solveMachine machines
+
in case sequence solutions of
+
Just ss -> show $ sum ss
+
Nothing -> "Failed"
+
+
buttonToRepeat :: Int -> Button -> [Int]
+
buttonToRepeat i button = go 0 button
+
where go n [] = repeat 0
+
go n (b:bs) =
+
if n == b then
+
(i : go (n+1) bs)
+
else
+
(0 : go (n+1) (b:bs))
+
+
type Matrix = (Int, Int, Map (Int,Int) (Ratio Int))
+
+
cols :: Matrix -> Int
+
cols (_, c, _) = c - 1
+
+
rows :: Matrix -> Int
+
rows (r, _, _) = r
+
+
mat :: Matrix -> Map (Int,Int) (Ratio Int)
+
mat (_, _, m) = m
+
+
minimum' :: Ord a => [a] -> Maybe a
+
minimum' [] = Nothing
+
minimum' as = Just $ minimum as
+
+
pivotCell :: Matrix -> Int -> Int -> Maybe (Int, Int)
+
pivotCell m minRow minCol =
+
minimum' [(i,j) | i <- [minCol..cols m - 1],
+
j <- [minRow..rows m - 1],
+
(mat m) Map.! (i,j) /= 0]
+
+
eraseColumn :: Int -> Int -> Matrix -> Matrix
+
eraseColumn i j m = error "TODO"
+
+
swapCols :: Int -> Int -> Matrix -> Matrix
+
swapCols i i' m = error "TODO"
+
-- let (r,c,d) = m
+
-- d' = Map.fromList
+
-- ([((i,j), d Map.! (i',j)) | j <- [0..rows - 1]] ++
+
-- [((i',j), d Map.! (i,j)) | j <- [0..rows - 1]])
+
-- in (r,c,Map.union d' d)
+
+
gaussJordan :: Map (Int,Int) (Ratio Int) -> Map (Int,Int) (Ratio Int)
+
gaussJordan mat = error "TODO" -- go mat 0 0
+
-- where go m minRow minCol =
+
-- case pivotCell m minRow minCol of
+
-- Nothing -> m
+
-- Just (i,j) ->
+
-- let m' = eraseColumn minCol minRow $
+
-- normalizeRow minCol minRow $
+
-- swapRows j minRow $
+
-- swapCols i minCol m
+
-- in go m' (minRow + 1) (minCol + 1)
+
+
solveMachine2 :: [Button] -> [Int] -> Maybe Int
+
solveMachine2 buttons goal = error "TODO"
+
-- let mat = Map.fromList [((i,j),1 % 1) | (i,b) <- zip [0..] buttons, j <- b]
+
-- g = Map.fromList $ zip [0..] (map (% 1) goal)
+
-- (mat', g', rank) = gaussJordan mat g
+
-- in Just 0
part2 :: String -> String
-
part2 _ = "Day 10 part 2"
+
part2 input =
+
let machines = parse input
+
solutions = map (\(_,b,g) -> solveMachine2 b g) machines
+
in case sequence solutions of
+
Just ss -> show $ sum ss
+
Nothing -> "Failed"
+78
src/Day10_part2.py
···
+
import sys
+
import itertools
+
+
def parseIntList(s):
+
return list(map(int, s[1:-1].split(",")))
+
+
def parseMachine(line):
+
words = line.split(" ")
+
buttons = list(map(parseIntList, words[1:-1]))
+
goal = parseIntList(words[-1])
+
return (buttons, goal)
+
+
def solveMachine(machine):
+
(buttons, goal) = machine
+
rows = len(goal)
+
cols = len(buttons) + 1
+
matrix = [[0 for _ in range(rows)] for _ in range(cols)]
+
for i,button in enumerate(buttons):
+
for b in button:
+
matrix[i][b] = 1
+
for i in range(rows):
+
matrix[cols-1][i] = -goal[i]
+
maxPresses = max(goal)
+
# Row reduce matrix
+
curRow,curCol = 0,0
+
freeVars = []
+
while curRow < rows and curCol < cols:
+
pivotRow = None
+
for i in range(curRow, rows):
+
if matrix[curCol][i] != 0:
+
pivotRow = i
+
break
+
if pivotRow is None:
+
freeVars.append(curCol)
+
curCol += 1
+
continue
+
for i in range(cols):
+
matrix[i][curRow], matrix[i][pivotRow] = matrix[i][pivotRow], matrix[i][curRow]
+
for j in range(rows):
+
if j == curRow:
+
continue
+
a,b = matrix[curCol][curRow],-matrix[curCol][j]
+
for i in range(cols):
+
matrix[i][j] = a*matrix[i][j] + b*matrix[i][curRow]
+
curRow += 1
+
curCol += 1
+
while curCol < cols:
+
freeVars.append(curCol)
+
curCol += 1
+
# The last col is going to be fixed at 1
+
freeVars.pop()
+
minMoves = maxPresses * len(buttons)
+
for freeAssignment in itertools.product(range(maxPresses+1), repeat=len(freeVars)):
+
varAssignment = [None for i in range(cols)]
+
varAssignment[-1] = 1
+
valid = True
+
for i,v in enumerate(freeVars):
+
varAssignment[v] = freeAssignment[i]
+
for j in range(rows-1,-1,-1):
+
nonZeroCols = [i for i in range(cols) if matrix[i][j] != 0]
+
if len(nonZeroCols) == 0:
+
continue
+
rhs = -sum([varAssignment[c]*matrix[c][j] for c in nonZeroCols[1:]])
+
lhs = matrix[nonZeroCols[0]][j]
+
if rhs % lhs != 0:
+
valid = False
+
break
+
if rhs // lhs < 0:
+
valid = False
+
break
+
varAssignment[nonZeroCols[0]] = rhs // lhs
+
if not valid:
+
continue
+
minMoves = min(minMoves, sum(varAssignment) - 1)
+
return minMoves
+
+
machines = [parseMachine(line) for line in open(sys.argv[1]).read().strip().split("\n")]
+
print(sum(map(solveMachine, machines)))
+56 -2
src/Day11.hs
···
module Day11 where
+
import Debug.Trace
+
import qualified Data.Map as Map
+
import Data.Map (Map)
+
+
parse :: String -> Map String [String]
+
parse = Map.fromList . map parseLine . lines
+
where parseLine :: String -> (String, [String])
+
parseLine s =
+
let w = words s
+
in (init $ head w, tail w)
+
+
pathCount :: Map String [String] -> String -> String -> Int
+
pathCount edges from to =
+
let m = Map.fromList [(v, ct v) | v <- Map.keys edges]
+
ct v = if v == to then 1 else sum $ map (m Map.!) (edges Map.! v)
+
in ct from
+
part1 :: String -> String
-
part1 _ = "Day 11 part 1"
+
part1 input =
+
let edges = Map.insert "out" [] $ parse input
+
in show $ pathCount edges "you" "out"
+
+
+
pathCount2 :: Map String [String] -> String -> String -> Int
+
pathCount2 edges from to =
+
let neitherMemo = Map.fromList [(v, neither v) | v <- Map.keys edges]
+
neither v = if v == to then 1 else sum $ map (neitherMemo Map.!) (edges Map.! v)
+
+
withDacMemo = Map.fromList [(v, withDac v) | v <- Map.keys edges]
+
withDac v = if v == to then
+
0
+
else if v == "dac" then
+
sum $ map (neitherMemo Map.!) (edges Map.! v)
+
else
+
sum $ map (withDacMemo Map.!) (edges Map.! v)
+
+
withFftMemo = Map.fromList [(v, withFft v) | v <- Map.keys edges]
+
withFft v = if v == to then
+
0
+
else if v == "fft" then
+
sum $ map (neitherMemo Map.!) (edges Map.! v)
+
else
+
sum $ map (withFftMemo Map.!) (edges Map.! v)
+
+
withBothMemo = Map.fromList [(v, withBoth v) | v <- Map.keys edges]
+
withBoth v = if v == to then
+
0
+
else if v == "dac" then
+
sum $ map (withFftMemo Map.!) (edges Map.! v)
+
else if v == "fft" then
+
sum $ map (withDacMemo Map.!) (edges Map.! v)
+
else
+
sum $ map (withBothMemo Map.!) (edges Map.! v)
+
in withBoth from
part2 :: String -> String
-
part2 _ = "Day 11 part 2"
+
part2 input =
+
let edges = Map.insert "out" [] $ parse input
+
in show $ pathCount2 edges "svr" "out"
+14 -2
src/Day12.hs
···
module Day12 where
+
parse :: String -> [(Int,Int,[Int])]
+
parse = map parseLine . drop 30 . lines
+
where parseLine line =
+
let w = words line
+
(size,counts) = (head w, map read $ tail w)
+
cols = read $ take 2 size
+
rows = read $ take 2 $ drop 3 size
+
in (cols, rows, counts)
+
part1 :: String -> String
-
part1 _ = "Day 12 part 1"
+
part1 input =
+
let regions = parse input
+
slack (cols, rows, counts) = cols * rows - 7 * (counts !! 0) - 7 * (counts !! 1) - 7 * (counts !! 2) - 5 * (counts !! 3) - 7 * (counts !! 4) - 6 * (counts !! 5)
+
in show $ length $ filter (> 0) $ map slack regions
part2 :: String -> String
-
part2 _ = "Day 12 part 2"
+
part2 _ = "You win!"
+37 -2
src/Day2.hs
···
module Day2 where
+
import Debug.Trace
+
import Util
+
+
isPartOneCandidate :: Int -> Bool
+
isPartOneCandidate n =
+
let s = show n
+
d = length s
+
in take (d `div` 2) s == drop (d `div` 2) s
+
+
isPartTwoCandidate :: Int -> Bool
+
isPartTwoCandidate n =
+
let s = show n
+
d = length s
+
cands = [concat $ replicate k $ take (d `div` k) s | k <- [2..d]]
+
in any (== s) cands
+
+
parseEntry :: [String] -> (Int, Int)
+
parseEntry (a : b : _) = (read a, read b)
+
parseEntry _ = error "Unreachable "
+
+
parse :: String -> [(Int, Int)]
+
parse s =
+
map (parseEntry . split '-') $
+
split ',' s
+
part1 :: String -> String
-
part1 _ = "Day 2 part 1"
+
part1 input = show $
+
sum $
+
filter isPartOneCandidate $
+
concat $
+
map (\(a,b) -> [a..b]) $
+
parse input
part2 :: String -> String
-
part2 _ = "Day 2 part 2"
+
part2 input = show $
+
sum $
+
filter isPartTwoCandidate $
+
concat $
+
map (\(a,b) -> [a..b]) $
+
parse input
+20 -2
src/Day3.hs
···
module Day3 where
+
import Data.Char
+
+
parse :: String -> [[Int]]
+
parse input = map (map digitToInt) (lines input)
+
+
maxJoltages :: [Int] -> [Int]
+
maxJoltages bank =
+
let update states k = zipWith max states (0 : [10*s + k | s <- states])
+
in foldl update (repeat 0) bank
+
part1 :: String -> String
-
part1 _ = "Day 3 part 1"
+
part1 input =
+
show $
+
sum $
+
map (\bank -> (maxJoltages bank) !! 2) $
+
parse input
part2 :: String -> String
-
part2 _ = "Day 3 part 2"
+
part2 input =
+
show $
+
sum $
+
map (\bank -> (maxJoltages bank) !! 12) $
+
parse input
+38 -2
src/Day4.hs
···
module Day4 where
+
import qualified Data.Map as Map
+
import Data.Map (Map)
+
import Util
+
+
parse :: String -> Grid
+
parse = toGrid
+
+
isOccupied :: Grid -> (Int,Int) -> Bool
+
isOccupied grid p = Map.lookup p grid == Just '@'
+
+
isAccessible :: Grid -> (Int,Int) -> Char -> Bool
+
isAccessible grid p '@' =
+
length (filter (isOccupied grid) (neighbors8 p)) < 4
+
isAccessible _ _ _ = False
+
part1 :: String -> String
-
part1 _ = "Day 4 part 1"
+
part1 input =
+
let grid = parse input
+
in show $
+
length $
+
Map.filterWithKey (isAccessible grid) grid
+
+
removeAccessible :: Grid -> Grid
+
removeAccessible grid =
+
let accessible = Map.filterWithKey (isAccessible grid) grid
+
in Map.difference grid accessible
+
+
removeAll :: Grid -> Grid
+
removeAll grid =
+
let grids = iterate removeAccessible grid
+
stabilized (x : y : gs) =
+
if Map.size x == Map.size y then
+
x
+
else
+
stabilized (y : gs)
+
in stabilized grids
part2 :: String -> String
-
part2 _ = "Day 4 part 2"
+
part2 input =
+
let grid = parse input
+
in show $ Map.size $ Map.difference grid (removeAll grid)
+43 -2
src/Day5.hs
···
module Day5 where
+
import Debug.Trace
+
import Data.Char
+
import Data.List
+
import Util
+
+
type Range = (Int,Int)
+
+
parse :: String -> ([Range], [Int])
+
parse s = foldl addLine ([], []) $ map (map read) $ map (splitOn (not . isDigit)) $ lines s
+
where addLine (ranges, queries) [lhs, rhs] = ((lhs, rhs) : ranges, queries)
+
addLine (ranges, queries) [q] = (ranges, q : queries)
+
addLine (ranges, queries) [] = (ranges, queries)
+
+
inRange :: Range -> Int -> Bool
+
inRange (lo,hi) q = lo <= q && q <= hi
+
+
inRanges :: [Range] -> Int -> Bool
+
inRanges rs q = any (\r -> inRange r q) rs
+
part1 :: String -> String
-
part1 _ = "Day 5 part 1"
+
part1 input =
+
let (rs,qs) = parse input
+
in show $
+
length $
+
filter (inRanges rs) qs
+
+
rangeUnion :: [Range] -> [Range]
+
rangeUnion rs =
+
let mergeWith cur [] = [cur]
+
mergeWith (lo,hi) ((lo',hi') : rest) =
+
if lo' <= hi then
+
mergeWith (lo, max hi hi') rest
+
else
+
(lo, hi) : mergeWith (lo', hi') rest
+
+
merge [] = []
+
merge (r : rest) = mergeWith r rest
+
in merge $ sort rs
part2 :: String -> String
-
part2 _ = "Day 5 part 2"
+
part2 input =
+
let (rs, _) = parse input
+
in show $
+
sum $
+
map (\(a,b) -> b-a+1) $
+
rangeUnion rs
+22 -3
src/Day6.hs
···
module Day6 where
+
import Data.List
+
import Debug.Trace
+
import Util
+
+
solveProblem :: ([Int], String) -> Int
+
solveProblem (terms, "+") = sum terms
+
solveProblem (terms, "*") = product terms
+
part1 :: String -> String
-
part1 _ = "Day 6 part 1"
-
+
part1 input =
+
let l = lines input
+
terms = map (map read) $ transpose $ map words $ init l
+
ops = words $ last l
+
in
+
show $
+
sum $
+
map solveProblem (zip terms ops)
+
part2 :: String -> String
-
part2 _ = "Day 6 part 2"
+
part2 input =
+
let l = lines input
+
terms :: [[Int]] = map (map read) $ splitOn null $ map trim $ transpose $ init l
+
ops = words $ last l
+
in show $ sum $ map solveProblem (zip terms ops)
+52 -2
src/Day7.hs
···
module Day7 where
+
import Util
+
import Data.Map (Map)
+
import qualified Data.Map as Map
+
import Data.Set (Set)
+
import qualified Data.Set as Set
+
import Debug.Trace
+
+
-- New beam positions and number of splits
+
splitBeams :: Grid -> (Set Point2, Int) -> (Set Point2, Int)
+
splitBeams g (beams,splits) =
+
foldl (\(beams',splits') (x,y) ->
+
if Map.lookup (x,y+1) g == Just '^' then
+
(Set.insert (x-1,y+1) $ Set.insert (x+1,y+1) beams', splits' + 1)
+
else
+
(Set.insert (x,y+1) beams', splits'))
+
(Set.empty, splits)
+
beams
+
part1 :: String -> String
-
part1 _ = "Day 7 part 1"
+
part1 input =
+
let grid = toGrid input
+
starts = map fst $ filter ((== 'S') . snd) (Map.toList grid)
+
rows = maximum $ map snd $ Map.keys grid
+
(_, splits) = foldl
+
(\(beams', splits') _ -> splitBeams grid (beams', splits'))
+
(Set.fromList starts, 0)
+
[0..rows]
+
in show splits
+
splitTime :: Grid -> Map Point2 Int -> Map Point2 Int
+
splitTime g beams =
+
Map.foldlWithKey (\beams' (x,y) times ->
+
if Map.lookup (x,y+1) g == Just '^' then
+
Map.insertWith (+) (x-1,y+1) times $
+
Map.insertWith (+) (x+1,y+1) times $
+
beams'
+
else
+
Map.insertWith (+) (x,y+1) times beams')
+
Map.empty
+
beams
+
part2 :: String -> String
-
part2 _ = "Day 7 part 2"
+
part2 input =
+
let grid = toGrid input
+
starts = map fst $ filter ((== 'S') . snd) (Map.toList grid)
+
rows = maximum $ map snd $ Map.keys grid
+
beams = foldl
+
(\beams' _ -> splitTime grid beams')
+
(Map.fromList [(s,1) | s <- starts])
+
[0..rows]
+
in
+
show $
+
sum $
+
map snd $
+
Map.toList beams
+67 -2
src/Day8.hs
···
module Day8 where
+
import Util
+
import Debug.Trace
+
import Data.Ord
+
import Data.Tuple (swap)
+
import Data.List (tails, sortBy, sort)
+
import Data.Map (Map)
+
import qualified Data.Map as Map
+
+
parseLine :: [String] -> Point3
+
parseLine [a,b,c] = (read a, read b, read c)
+
+
parse :: String -> [Point3]
+
parse s =
+
map (parseLine . split ',') $ lines s
+
+
dist2 :: Point3 -> Point3 -> Int
+
dist2 (x,y,z) (x',y',z') = (x-x') ^ 2 + (y-y') ^ 2 + (z-z') ^ 2
+
+
pairs :: [a] -> [(a,a)]
+
pairs xs = [(x, y) | (x:ys) <- tails xs, y <- ys]
+
+
merge :: (Point3, Point3) -> Map Point3 Int -> Map Point3 Int
+
merge (p, q) m =
+
let pid = m Map.! p
+
qid = m Map.! q
+
in if pid == qid then
+
m
+
else
+
Map.map (\c -> if c == pid then qid else c) m
+
part1 :: String -> String
-
part1 _ = "Day 8 part 1"
+
part1 input =
+
let boxes = parse input
+
connectionOrder =
+
dropWhile (\(p,q) -> p == q) $
+
sortBy (comparing (\(p,q) -> dist2 p q)) $
+
pairs boxes
+
circuits =
+
foldl (\ids' edge -> merge edge ids')
+
(Map.fromList $ zip boxes [1..])
+
(take 1000 connectionOrder)
+
sizes =
+
reverse $
+
sort $
+
map snd $
+
Map.toList $
+
Map.foldl (\sizes' c -> Map.insertWith (+) c 1 sizes')
+
Map.empty
+
circuits
+
in show $ product $ take 3 sizes
part2 :: String -> String
-
part2 _ = "Day 8 part 2"
+
part2 input =
+
let boxes = parse input
+
connectionOrder =
+
dropWhile (\(p,q) -> p == q) $
+
sortBy (comparing (\(p,q) -> dist2 p q)) $
+
pairs boxes
+
circuits =
+
scanl (\ids' edge -> merge edge ids')
+
(Map.fromList $ zip boxes [1..])
+
connectionOrder
+
merges =
+
filter (\((p,q),ids) -> ids Map.! p /= ids Map.! q)
+
$ zip connectionOrder circuits
+
((p,q),_) = merges !! (length boxes - 2)
+
(x,_,_) = p
+
(x',_,_) = q
+
in show (x * x')
+
+38 -2
src/Day9.hs
···
module Day9 where
+
import Util
+
+
parseLine :: [String] -> Point2
+
parseLine [x,y] = (read x, read y)
+
+
parse :: String -> [Point2]
+
parse s = map (parseLine . split ',') $ lines s
+
+
area :: Point2 -> Point2 -> Int
+
area (x,y) (z,w) = (abs (x - z) + 1) * (abs (y - w) + 1)
+
part1 :: String -> String
-
part1 _ = "Day 9 part 1"
+
part1 input =
+
let pts = parse input
+
in show $
+
maximum $
+
[ area p q | p <- pts, q <- pts ]
+
+
uniq :: Eq a => [a] -> [a]
+
uniq [] = []
+
uniq [a] = [a]
+
uniq (a:b:cs) = if a == b then uniq (b:cs) else a : (uniq (b:cs))
+
+
edgeIntersect :: (Point2, Point2) -> (Point2, Point2) -> Bool
+
edgeIntersect (p,q) (r,s) =
+
max (fst r) (fst s) > min (fst p) (fst q) &&
+
min (fst r) (fst s) < max (fst p) (fst q) &&
+
max (snd r) (snd s) > min (snd p) (snd q) &&
+
min (snd r) (snd s) < max (snd p) (snd q)
+
+
containsRect :: Point2 -> Point2 -> [(Point2,Point2)] -> Bool
+
containsRect p q edges =
+
not $ or [edgeIntersect (p,q) e | e <- edges]
part2 :: String -> String
-
part2 _ = "Day 9 part 2"
+
part2 input =
+
let pts = parse input
+
edges = zip pts ((tail pts) ++ [head pts])
+
in show $
+
maximum $
+
[area p q | p <- pts, q <- pts, containsRect p q edges]
+36
src/Util.hs
···
+
module Util where
+
+
import Data.Char (isSpace)
+
import qualified Data.Map as Map
+
import Data.Map (Map)
+
+
splitOn :: (a -> Bool) -> [a] -> [[a]]
+
splitOn p s = case dropWhile p s of
+
[] -> []
+
s' -> w : splitOn p s''
+
where (w, s'') = break p s'
+
+
split :: (Eq a) => a -> [a] -> [[a]]
+
split c = splitOn (== c)
+
+
enumerate :: [a] -> [(Int,a)]
+
enumerate l = zip [0..] l
+
+
type Point2 = (Int,Int)
+
type Point3 = (Int,Int,Int)
+
type Grid = Map Point2 Char
+
+
toGrid :: String -> Grid
+
toGrid s = Map.fromList [((x,y),c) | (y,r) <- enumerate (lines s), (x,c) <- enumerate r]
+
+
neighbors8 :: Point2 -> [Point2]
+
neighbors8 (x,y) =
+
[(x-1,y-1),(x,y-1),(x+1,y-1),(x-1,y),(x+1,y),(x-1,y+1),(x,y+1),(x+1,y+1)]
+
+
neighbors4 :: Point2 -> [Point2]
+
neighbors4 (x,y) =
+
[(x-1,y), (x+1,y), (x,y-1), (x,y+1)]
+
+
trim :: String -> String
+
trim = f . f
+
where f = reverse . dropWhile isSpace