Haskell:
import Data.Array.Unboxed
import Data.Ix
count x = length . filter (==x)
adjacents :: ((Int, Int), (Int, Int)) -> (Int, Int) -> [(Int, Int)]
adjacents bds (i, j) = [ (x, y)
| x <- [i-1 .. i+1]
, y <- [j-1 .. j+1]
, (x, y) /= (i, j)
, inRange bds (x, y)
]
nextState :: (UArray (Int, Int) Char -> (Int, Int) -> String -> Char)
-> UArray (Int, Int) Char -> UArray (Int, Int) Char
nextState f a = array bds [ (xy, f a xy adjStates)
| xy <- range bds
, let adjStates = map (a !) $ adjacents bds xy
]
where bds = bounds a
part1 = count '#' . elems . (!! 100) . iterate (nextState f) . makeGrid
where makeGrid = listArray ((0, 0), (99, 99)) . concat . lines
f a xy adjStates
| a ! xy == '#' && not (lightsOn `elem` [2, 3]) = '.'
| a ! xy == '.' && lightsOn == 3 = '#'
| otherwise = a ! xy
where lightsOn = count '#' adjStates
part2 = count '#' . elems . (!! 100) . iterate (nextState f) . makeGrid
where corners = [(0, 0), (0, 99), (99, 0), (99, 99)]
bds = ((0, 0), (99, 99))
makeGrid = accumArray (flip const) '.' bds . (++ (zip corners $ repeat '#'))
. zip (range bds) . concat . lines
f a xy adjStates
| xy `elem` corners = '#'
| a ! xy == '#' && not (lightsOn `elem` [2, 3]) = '.'
| a ! xy == '.' && lightsOn == 3 = '#'
| otherwise = a ! xy
where lightsOn = count '#' adjStates
main = do
input <- readFile "input.txt"
print $ part1 input
print $ part2 input