Done day 21
authorNeil Smith <neil.git@njae.me.uk>
Thu, 21 Dec 2017 21:41:54 +0000 (21:41 +0000)
committerNeil Smith <neil.git@njae.me.uk>
Thu, 21 Dec 2017 21:41:54 +0000 (21:41 +0000)
advent-of-code.cabal
data/advent21.txt [new file with mode: 0644]
problems/day21.html [new file with mode: 0644]
src/advent21/advent21.hs [new file with mode: 0644]
src/advent21/advent21.ipynb [new file with mode: 0644]

index e2dd2a8e561dc3353640015390f5e8b747560564..f9fafceef353127fd5ae21497c651935ba80c908 100644 (file)
@@ -219,3 +219,12 @@ executable advent20
                      , text
                      , megaparsec
                      , vector
+
+executable advent21
+  hs-source-dirs:      src/advent21
+  main-is:             advent21.hs
+  default-language:    Haskell2010
+  build-depends:       base >= 4.7 && < 5
+                     , containers
+                     , text
+                     , megaparsec
diff --git a/data/advent21.txt b/data/advent21.txt
new file mode 100644 (file)
index 0000000..97f767a
--- /dev/null
@@ -0,0 +1,108 @@
+../.. => .../.##/##.
+#./.. => .##/.##/#..
+##/.. => ..#/.../###
+.#/#. => #.#/..#/##.
+##/#. => .#./.#./..#
+##/## => #.#/#../###
+.../.../... => ..../#.../.##./..#.
+#../.../... => ####/#.##/##.#/..#.
+.#./.../... => ..##/..##/..##/..##
+##./.../... => ..../..#./##../##.#
+#.#/.../... => ##.#/..../####/...#
+###/.../... => .#.#/.###/.#../.#.#
+.#./#../... => .###/#.#./...#/##..
+##./#../... => #.##/#.../####/###.
+..#/#../... => ####/...#/...#/#.##
+#.#/#../... => .#../##../..##/..#.
+.##/#../... => .#../..##/..../.##.
+###/#../... => #.../..#./.#.#/#..#
+.../.#./... => #.#./.#.#/.###/...#
+#../.#./... => ###./.#../...#/.#..
+.#./.#./... => ##.#/.#../#..#/##..
+##./.#./... => #..#/...#/.#.#/###.
+#.#/.#./... => .##./#.../#..#/.###
+###/.#./... => .#.#/##.#/..../##.#
+.#./##./... => ##.#/#.##/.#.#/#.##
+##./##./... => #.##/..#./..#./.##.
+..#/##./... => ..../#.../..#./..##
+#.#/##./... => .##./####/####/####
+.##/##./... => #.##/####/#.##/#..#
+###/##./... => .#../.###/##../...#
+.../#.#/... => ...#/...#/#.##/####
+#../#.#/... => ..#./..#./###./.##.
+.#./#.#/... => .##./##../.###/.#.#
+##./#.#/... => #.#./.#../.##./...#
+#.#/#.#/... => ##.#/..##/#.../##.#
+###/#.#/... => ..##/##../.#.#/..##
+.../###/... => .#../#.../.##./....
+#../###/... => ..##/..##/...#/.##.
+.#./###/... => #..#/..#./#.#./..##
+##./###/... => #.##/.#../##.#/##.#
+#.#/###/... => ####/###./.##./...#
+###/###/... => #..#/#.##/..../.##.
+..#/.../#.. => #.#./.#../##../..#.
+#.#/.../#.. => ##.#/####/##../.#.#
+.##/.../#.. => ####/##../#..#/..#.
+###/.../#.. => ##../..#./####/##.#
+.##/#../#.. => ##../#.#./###./..##
+###/#../#.. => ..../.#../#..#/...#
+..#/.#./#.. => ..#./...#/.###/.#.#
+#.#/.#./#.. => ###./..../#.#./###.
+.##/.#./#.. => ####/#.##/.#.#/.#..
+###/.#./#.. => ###./#.##/##../####
+.##/##./#.. => ##.#/..##/..#./.#..
+###/##./#.. => ##.#/.##./.###/.##.
+#../..#/#.. => #.../###./##.#/#..#
+.#./..#/#.. => ..##/.###/...#/..#.
+##./..#/#.. => ##../#.#./...#/.#..
+#.#/..#/#.. => ..#./###./##../.###
+.##/..#/#.. => #.../.##./..../#.#.
+###/..#/#.. => .#.#/#.##/#.##/..#.
+#../#.#/#.. => ..##/..##/#.../####
+.#./#.#/#.. => #.../...#/..../..##
+##./#.#/#.. => ###./..##/.#../.##.
+..#/#.#/#.. => ...#/..##/..#./.#..
+#.#/#.#/#.. => #.#./.#../..../##..
+.##/#.#/#.. => ..#./.###/##.#/....
+###/#.#/#.. => #.##/..##/...#/##..
+#../.##/#.. => #.#./##../###./.#.#
+.#./.##/#.. => .###/#..#/.##./....
+##./.##/#.. => .#.#/.#../.###/.##.
+#.#/.##/#.. => .#../..##/###./#.##
+.##/.##/#.. => ##../.##./..#./.#..
+###/.##/#.. => .#.#/..#./#..#/.###
+#../###/#.. => #.##/#..#/.#.#/#.#.
+.#./###/#.. => #.../#..#/#.../.#.#
+##./###/#.. => ##../####/##../.###
+..#/###/#.. => #.../..../####/##.#
+#.#/###/#.. => ...#/..../...#/..##
+.##/###/#.. => .#../####/#.##/.#..
+###/###/#.. => ###./.#.#/#.../##..
+.#./#.#/.#. => ...#/##../####/...#
+##./#.#/.#. => ####/#..#/###./#.##
+#.#/#.#/.#. => .###/#..#/..#./...#
+###/#.#/.#. => ###./.###/##.#/###.
+.#./###/.#. => #..#/#.../..#./####
+##./###/.#. => #.../..../#..#/..##
+#.#/###/.#. => #..#/.#.#/#.../##..
+###/###/.#. => .#.#/..../.#.#/#.##
+#.#/..#/##. => .#../..##/...#/###.
+###/..#/##. => .###/..#./##.#/##.#
+.##/#.#/##. => ####/#.##/.##./##..
+###/#.#/##. => #..#/#..#/####/#.##
+#.#/.##/##. => .###/#.#./#..#/.#.#
+###/.##/##. => #.#./#.#./#.##/..##
+.##/###/##. => ####/###./##.#/##.#
+###/###/##. => ##../..##/#.#./#...
+#.#/.../#.# => .#../###./.###/##.#
+###/.../#.# => ..../.#.#/#..#/##..
+###/#../#.# => ..#./#.../.##./...#
+#.#/.#./#.# => ...#/#.../##.#/.##.
+###/.#./#.# => ..../..../#.#./##.#
+###/##./#.# => .#../...#/...#/###.
+#.#/#.#/#.# => ...#/#.../##../.###
+###/#.#/#.# => #.../...#/.#../#.##
+#.#/###/#.# => ..../.##./..../##..
+###/###/#.# => .##./.#.#/#.##/.##.
+###/#.#/### => #.#./####/.##./.##.
+###/###/### => .#.#/..##/#.##/.##.
\ No newline at end of file
diff --git a/problems/day21.html b/problems/day21.html
new file mode 100644 (file)
index 0000000..4d1bc0f
--- /dev/null
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html lang="en-us">
+<head>
+<meta charset="utf-8"/>
+<title>Day 21 - Advent of Code 2017</title>
+<!--[if lt IE 9]><script src="/static/html5.js"></script><![endif]-->
+<link href='//fonts.googleapis.com/css?family=Source+Code+Pro:300&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
+<link rel="stylesheet" type="text/css" href="/static/style.css?12"/>
+<link rel="stylesheet alternate" type="text/css" href="/static/highcontrast.css?0" title="High Contrast"/>
+<link rel="shortcut icon" href="/favicon.ico?2"/>
+</head><!--
+
+
+
+
+Oh, hello!  Funny seeing you here.
+
+I appreciate your enthusiasm, but you aren't going to find much down here.
+There certainly aren't clues to any of the puzzles.  The best surprises don't
+even appear in the source until you unlock them for real.
+
+Please be careful with automated requests; I'm not Google, and I can only take
+so much traffic.  Please be considerate so that everyone gets to play.
+
+If you're curious about how Advent of Code works, it's running on some custom
+Perl code. Other than a few integrations (auth, analytics, ads, social media),
+I built the whole thing myself, including the design, animations, prose, and
+all of the puzzles.
+
+The puzzles probably took the longest; the easiest ones took an hour or two
+each, but the harder ones took 4-5 hours, and a few even longer than that. A
+lot of effort went into building this thing - I hope you're enjoying playing it
+as much as I enjoyed making it for you!
+
+If you'd like to hang out, I'm @ericwastl on Twitter.
+
+- Eric Wastl
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+-->
+<body>
+<header><div><h1 class="title-global"><a href="/">Advent of Code</a></h1><nav><ul><li><a href="/2017/about">[About]</a></li><li><a href="/2017/support">[AoC++]</a></li><li><a href="/2017/events">[Events]</a></li><li><a href="/2017/settings">[Settings]</a></li><li><a href="/2017/auth/logout">[Log Out]</a></li></ul></nav><div class="user">Neil Smith <span class="supporter">(AoC++)</span> <span class="star-count">42*</span></div></div><div><h1 class="title-event">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="title-event-wrap"></span><a href="/2017">2017</a><span class="title-event-wrap"></span></h1><nav><ul><li><a href="/2017">[Calendar]</a></li><li><a href="/2017/leaderboard">[Leaderboard]</a></li><li><a href="/2017/stats">[Stats]</a></li><li><a href="/2017/sponsors">[Sponsors]</a></li></ul></nav></div></header>
+
+<div id="sidebar">
+<div id="sponsor"><div class="quiet">Our <a href="/2017/sponsors">sponsors</a> help make Advent of Code possible:</div><p><a href="http://kx.com/" target="_blank" onclick="if(ga)ga('send','event','sponsor','click',this.href);" rel="noopener">Kx Systems</a> - kdb+, the in-memory time series technology standard</p></div>
+<p class="quiet">By popular demand, there are now AoC-themed objects available (until Jan. 3rd)! Get them shipped <a href="https://teespring.com/advent-of-code" target="_blank">from the US</a> or <a href="https://teespring.com/advent-of-code-eu" target="_blank">from Europe</a>.</p>
+
+</div><!--/sidebar-->
+
+<main>
+<article class="day-desc"><h2>--- Day 21: Fractal Art ---</h2><p>You find a program trying to generate some art. It uses a strange process that involves <span title="This technique is also often used on TV.">repeatedly enhancing</span> the detail of an image through a set of rules.</p>
+<p>The image consists of a two-dimensional square grid of pixels that are either on (<code>#</code>) or off (<code>.</code>). The program always begins with this pattern:</p>
+<pre><code>.#.
+..#
+###
+</code></pre>
+<p>Because the pattern is both <code>3</code> pixels wide and <code>3</code> pixels tall, it is said to have a <em>size</em> of <code>3</code>.</p>
+<p>Then, the program repeats the following process:</p>
+<ul>
+<li>If the size is evenly divisible by <code>2</code>, break the pixels up into <code>2x2</code> squares, and convert each <code>2x2</code> square into a <code>3x3</code> square by following the corresponding <em>enhancement rule</em>.</li>
+<li>Otherwise, the size is evenly divisible by <code>3</code>; break the pixels up into <code>3x3</code> squares, and convert each <code>3x3</code> square into a <code>4x4</code> square by following the corresponding <em>enhancement rule</em>.</li>
+</ul>
+<p>Because each square of pixels is replaced by a larger one, the image gains pixels and so its <em>size</em> increases.</p>
+<p>The artist's book of enhancement rules is nearby (your puzzle input); however, it seems to be missing rules.  The artist explains that sometimes, one must <em>rotate</em> or <em>flip</em> the input pattern to find a match. (Never rotate or flip the output pattern, though.) Each pattern is written concisely: rows are listed as single units, ordered top-down, and separated by slashes. For example, the following rules correspond to the adjacent patterns:</p>
+<pre><code>../.#  =  ..
+          .#
+
+                .#.
+.#./..#/###  =  ..#
+                ###
+
+                        #..#
+#..#/..../#..#/.##.  =  ....
+                        #..#
+                        .##.
+</code></pre>
+<p>When searching for a rule to use, rotate and flip the pattern as necessary.  For example, all of the following patterns match the same rule:</p>
+<pre><code>.#.   .#.   #..   ###
+..#   #..   #.#   ..#
+###   ###   ##.   .#.
+</code></pre>
+<p>Suppose the book contained the following two rules:</p>
+<pre><code>../.# => ##./#../...
+.#./..#/### => #..#/..../..../#..#
+</code></pre>
+<p>As before, the program begins with this pattern:</p>
+<pre><code>.#.
+..#
+###
+</code></pre>
+<p>The size of the grid (<code>3</code>) is not divisible by <code>2</code>, but it is divisible by <code>3</code>. It divides evenly into a single square; the square matches the second rule, which produces:</p>
+<pre><code>#..#
+....
+....
+#..#
+</code></pre>
+<p>The size of this enhanced grid (<code>4</code>) is evenly divisible by <code>2</code>, so that rule is used. It divides evenly into four squares:</p>
+<pre><code>#.|.#
+..|..
+--+--
+..|..
+#.|.#
+</code></pre>
+<p>Each of these squares matches the same rule (<code>../.# => ##./#../...</code>), three of which require some flipping and rotation to line up with the rule. The output for the rule is the same in all four cases:</p>
+<pre><code>##.|##.
+#..|#..
+...|...
+---+---
+##.|##.
+#..|#..
+...|...
+</code></pre>
+<p>Finally, the squares are joined into a new grid:</p>
+<pre><code>##.##.
+#..#..
+......
+##.##.
+#..#..
+......
+</code></pre>
+<p>Thus, after <code>2</code> iterations, the grid contains <code>12</code> pixels that are <em>on</em>.</p>
+<p><em>How many pixels stay on</em> after <code>5</code> iterations?</p>
+</article>
+<p>Your puzzle answer was <code>158</code>.</p><article class="day-desc"><h2>--- Part Two ---</h2><p><em>How many pixels stay on</em> after <code>18</code> iterations?</p>
+</article>
+<p>Your puzzle answer was <code>2301762</code>.</p><p class="day-success">Both parts of this puzzle are complete! They provide two gold stars: **</p>
+<p>At this point, you should <a href="/2017">return to your advent calendar</a> and try another puzzle.</p>
+<p>If you still want to see it, you can <a href="21/input" target="_blank">get your puzzle input</a>.</p>
+<p>You can also <span class="share">[Share<span class="share-content">on
+  <a href="https://twitter.com/intent/tweet?text=I%27ve+completed+%22Fractal+Art%22+%2D+Day+21+%2D+Advent+of+Code+2017&amp;url=http%3A%2F%2Fadventofcode%2Ecom%2F2017%2Fday%2F21&amp;related=ericwastl&amp;hashtags=AdventOfCode" target="_blank">Twitter</a>
+  <a href="https://plus.google.com/share?url=http%3A%2F%2Fadventofcode%2Ecom%2F2017%2Fday%2F21" target="_blank">Google+</a>
+  <a href="http://www.reddit.com/submit?url=http%3A%2F%2Fadventofcode%2Ecom%2F2017%2Fday%2F21&amp;title=I%27ve+completed+%22Fractal+Art%22+%2D+Day+21+%2D+Advent+of+Code+2017" target="_blank">Reddit</a
+></span>]</span> this puzzle.</p>
+</main>
+
+<!-- ga -->
+<script>
+(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+ga('create', 'UA-69522494-1', 'auto');
+ga('send', 'pageview');
+</script>
+<!-- /ga -->
+</body>
+</html>
\ No newline at end of file
diff --git a/src/advent21/advent21.hs b/src/advent21/advent21.hs
new file mode 100644 (file)
index 0000000..62b5b5c
--- /dev/null
@@ -0,0 +1,170 @@
+{-# LANGUAGE NegativeLiterals #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE BangPatterns #-}
+
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO
+
+import Text.Megaparsec hiding (State)
+import qualified Text.Megaparsec.Lexer as L
+import Text.Megaparsec.Text (Parser)
+import qualified Control.Applicative as CA
+-- import qualified Data.Functor as F
+
+import qualified Data.Map.Strict as M
+import Data.Map.Strict ((!))
+
+import Data.List 
+
+
+type Grid = M.Map (Int, Int) Bool
+type ExplodedGrid = M.Map (Int, Int) Grid
+
+data Rule = Rule Grid Grid deriving (Eq, Show)
+
+rulePre (Rule g _) = g
+rulePost (Rule _ g) = g
+
+
+initialGrid = case parse gridP "" ".#./..#/###" of 
+                Left _ -> M.empty 
+                Right g -> g
+
+
+main :: IO ()
+main = do 
+        text <- TIO.readFile "data/advent21.txt"
+        let rules = readRules text
+        print $ countLit $ nthApplication rules 5
+        print $ countLit $ nthApplication rules 18
+
+
+readRules :: Text -> [Rule]
+readRules = expandRules . successfulParse
+
+expandRules = concatMap expandRule
+expandRule rule = [Rule l (rulePost rule) | l <- allArrangements (rulePre rule)]
+
+reflectH g = M.fromList [((r, c) , M.findWithDefault False (rm - r, c) g) | r <- [0..rm], c <- [0..cm] ]
+    where (rm, cm) = bounds g
+
+reflectV g = M.fromList [((r, c) , M.findWithDefault False (r, cm - c) g) | r <- [0..rm], c <- [0..cm] ]
+    where (rm, cm) = bounds g
+
+transposeG g = M.fromList [((c, r) , M.findWithDefault False (r, c) g) | r <- [0..rm], c <- [0..cm] ]
+    where (rm, cm) = bounds g
+
+allArrangements grid = map (\f -> f grid) [ id
+                                          , reflectH
+                                          , reflectV
+                                          , transposeG
+                                          , reflectH . transposeG
+                                          , reflectV . transposeG
+                                          , reflectH . reflectV . transposeG
+                                          , reflectV . reflectH
+                                          ]
+
+
+
+
+countLit = M.size . M.filter id
+
+
+applyOnce rules g = contractExploded $ M.map (apply rules) $ explodeGrid g
+
+nthApplication rules n = (!! n) $ iterate (applyOnce rules) initialGrid
+
+
+
+apply rules grid = rulePost thisRule
+    where ri = head $ findIndices (\r -> rulePre r == grid) rules
+          thisRule = rules!!ri
+
+
+explodeGrid :: Grid -> ExplodedGrid
+explodeGrid g = if (rm + 1) `rem` 2 == 0 
+                then explodeGrid' 2 g
+                else explodeGrid' 3 g
+    where (rm, cm) = bounds g
+
+contractExploded :: ExplodedGrid -> Grid
+contractExploded gs = foldl1 (>|<) $ map (foldl1 (>-<)) rows
+    where rows = explodedRows gs
+
+
+explodeGrid' :: Int -> Grid -> ExplodedGrid
+explodeGrid' n g = M.fromList [((bigR, bigC), subGrid n g bigR bigC) | bigR <- [0..bigRm], bigC <- [0..bigCm]]
+    where (rm, cm) = bounds g
+          bigRm = (rm + 1) `div` n - 1
+          bigCm = (cm + 1) `div` n - 1
+
+
+subGrid :: Int -> Grid -> Int -> Int -> Grid
+subGrid n g bigR bigC = M.fromList [ ((r, c), 
+                                      M.findWithDefault False (r + rStep, c + cStep) g) 
+                                   | r <- [0..(n - 1)], c <- [0..(n - 1)]
+                                   ]
+    where rStep = bigR * n
+          cStep = bigC * n
+
+
+explodedRows eg = [M.filterWithKey (\(r, _) _ -> r == row) eg | row <- [0..rowMax] ]
+    where (rowMax, _) = bounds eg
+
+(>-<) g1 g2 = M.union g1 g2'
+    where (_, cm) = bounds g1
+          g2' = M.mapKeys (\(r, c) -> (r, c + cm + 1)) g2
+
+(>|<) g1 g2 = M.union g1 g2'
+    where (rm, _) = bounds g1
+          g2' = M.mapKeys (\(r, c) -> (r + rm + 1, c)) g2      
+
+
+
+
+
+bounds :: M.Map (Int, Int) a -> (Int, Int)
+bounds grid = (maximum $ map fst $ M.keys grid, maximum $ map snd $ M.keys grid)
+
+
+showGrid g = unlines [[showGChar $ M.findWithDefault False (r, c) g | 
+                c <- [0..cm] ] | r <- [0..rm] ]
+    where (rm, cm) = bounds g
+          showGChar True = '#'
+          showGChar False = '.'
+
+
+onlySpace = (char ' ') <|> (char '\t')
+
+sc :: Parser ()
+sc = L.space (skipSome onlySpace) CA.empty CA.empty
+
+lexeme  = L.lexeme sc
+
+symbol = L.symbol sc
+rowSep = symbol "/"
+ruleJoin = symbol "=>"
+
+present = id True <$ symbol "#"
+absent = id False <$ symbol "."
+
+rulesP = ruleP `sepBy` space
+ruleP = Rule <$> gridP <*> (ruleJoin *> gridP)
+
+gridP = gridify <$> rowP `sepBy` rowSep
+    where gridify g = M.fromList $ concat 
+                                    [map (\(c, v) -> ((r, c), v)) nr | 
+                                             (r, nr) <- zip [0..] 
+                                                            [zip [0..] r | r <- g]]
+
+
+rowP = some (present <|> absent)
+successfulParse :: Text -> [Rule]
+successfulParse input = 
+        case parse rulesP "input" input of
+                Left  _error -> [] 
+                Right instructions  -> instructions
\ No newline at end of file
diff --git a/src/advent21/advent21.ipynb b/src/advent21/advent21.ipynb
new file mode 100644 (file)
index 0000000..82fb65b
--- /dev/null
@@ -0,0 +1,1276 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "{-# LANGUAGE NegativeLiterals #-}\n",
+    "{-# LANGUAGE FlexibleContexts #-}\n",
+    "{-# LANGUAGE OverloadedStrings #-}\n",
+    "{-# LANGUAGE TypeFamilies #-}\n",
+    "{-# LANGUAGE BangPatterns #-}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import Data.Text (Text)\n",
+    "import qualified Data.Text as T\n",
+    "import qualified Data.Text.IO as TIO\n",
+    "\n",
+    "import Text.Megaparsec hiding (State)\n",
+    "import qualified Text.Megaparsec.Lexer as L\n",
+    "import Text.Megaparsec.Text (Parser)\n",
+    "import qualified Control.Applicative as CA\n",
+    "-- import Data.Functor (void)\n",
+    "\n",
+    "import qualified Data.Map.Strict as M\n",
+    "import Data.Map.Strict ((!))\n",
+    "\n",
+    "-- import Data.Vector ((!), (//))\n",
+    "-- import qualified Data.Vector as V\n",
+    "\n",
+    "import Data.List \n",
+    "import qualified Data.Functor as F"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "type Grid = M.Map (Int, Int) Bool\n",
+    "-- type Grid = [[Char]]\n",
+    "-- type Grid = [[Bool]]\n",
+    "\n",
+    "type ExplodedGrid = M.Map (Int, Int) Grid\n",
+    "\n",
+    "data Rule = Rule Grid Grid deriving (Eq, Show)\n",
+    "\n",
+    "rulePre (Rule g _) = g\n",
+    "rulePost (Rule _ g) = g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 38,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "onlySpace = (char ' ') <|> (char '\\t')\n",
+    "\n",
+    "sc :: Parser ()\n",
+    "sc = L.space (skipSome onlySpace) CA.empty CA.empty"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "lexeme  = L.lexeme sc\n",
+    "\n",
+    "symbol = L.symbol sc\n",
+    "rowSep = symbol \"/\"\n",
+    "ruleJoin = symbol \"=>\"\n",
+    "\n",
+    "-- present :: Parser Bool\n",
+    "present = id True <$ symbol \"#\"\n",
+    "\n",
+    "-- absent :: Parser Bool\n",
+    "absent = id False <$ symbol \".\"\n",
+    "\n",
+    "rulesP = ruleP `sepBy` space\n",
+    "ruleP = Rule <$> gridP <*> (ruleJoin *> gridP)\n",
+    "\n",
+    "gridP = gridify <$> rowP `sepBy` rowSep\n",
+    "    where gridify g = M.fromList $ concat \n",
+    "                                    [map (\\(c, v) -> ((r, c), v)) nr | \n",
+    "                                             (r, nr) <- zip [0..] \n",
+    "                                                            [zip [0..] r | r <- g]]\n",
+    "\n",
+    "\n",
+    "rowP = some (present <|> absent)\n",
+    " \n",
+    "successfulParse :: Text -> [Rule]\n",
+    "successfulParse input = \n",
+    "        case parse rulesP \"input\" input of\n",
+    "                Left  _error -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err\n",
+    "                Right instructions  -> instructions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Rule (fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]) (fromList [((0,0),False),((0,1),True),((0,2),True),((1,0),False),((1,1),True),((1,2),True),((2,0),True),((2,1),False),((2,2),False)])"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "parseTest ruleP \"#./.. => .##/.##/#..\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Rule (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),True),((2,0),False),((2,1),False),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),True),((0,3),False),((1,0),False),((1,1),True),((1,2),False),((1,3),False),((2,0),False),((2,1),True),((2,2),True),((2,3),False),((3,0),False),((3,1),False),((3,2),False),((3,3),True)])"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "parseTest ruleP \"##./#.#/... => #.#./.#../.##./...#\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "testRule = head $ successfulParse  \"##./#.#/... => #.#./.#../.##./...#\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Rule (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),True),((2,0),False),((2,1),False),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),True),((0,3),False),((1,0),False),((1,1),True),((1,2),False),((1,3),False),((2,0),False),((2,1),True),((2,2),True),((2,3),False),((3,0),False),((3,1),False),((3,2),False),((3,3),True)])"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "testRule"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[Rule (fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]) (fromList [((0,0),False),((0,1),True),((0,2),True),((1,0),False),((1,1),True),((1,2),True),((2,0),True),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),True),((2,0),False),((2,1),False),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),True),((0,3),False),((1,0),False),((1,1),True),((1,2),False),((1,3),False),((2,0),False),((2,1),True),((2,2),True),((2,3),False),((3,0),False),((3,1),False),((3,2),False),((3,3),True)])]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "parseTest rulesP \"#./.. => .##/.##/#..\\n##./#.#/... => #.#./.#../.##./...#\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Rule (fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]) \n",
+    "     (fromList [((0,0),False),((0,1),True),((0,2),True),((1,0),False),((1,1),True),((1,2),True),((2,0),True),((2,1),False),((2,2),False),((2,3),True),((2,4),True),((2,5),False),((3,0),True),((3,1),False),((3,2),True),((4,0),False),((4,1),False),((4,2),False)]),\n",
+    "     \n",
+    "Rule (fromList []) \n",
+    "     (fromList [((0,0),True),((0,1),False),((0,2),True),((0,3),False),((1,0),False),((1,1),True),((1,2),False),((1,3),False),((2,0),False),((2,1),True),((2,2),True),((2,3),False),((3,0),False),((3,1),False),((3,2),False),((3,3),True)])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "g = [[False,True,True],[False,True,True],[True,False,False]]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[((0,0),False),((0,1),True),((0,2),True),((1,0),False),((1,1),True),((1,2),True),((2,0),True),((2,1),False),((2,2),False)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "concat [map (\\(c, v) -> ((r, c), v)) nr | (r, nr) <- zip [0..] [zip [0..] r | r <- g]]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 53,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bounds :: M.Map (Int, Int) a -> (Int, Int)\n",
+    "bounds grid = (maximum $ map fst $ M.keys grid, maximum $ map snd $ M.keys grid)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(3,3)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "bounds (rulePost testRule)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "showGrid g = unlines [[showChar $ M.findWithDefault False (r, c) g | \n",
+    "                c <- [0..cm] ] | r <- [0..rm] ]\n",
+    "    where (rm, cm) = bounds g\n",
+    "          showChar True = '#'\n",
+    "          showChar False = '.'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "#.#.\n",
+       ".#..\n",
+       ".##.\n",
+       "...#"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ rulePost testRule"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "initialGrid = case parse gridP \"\" \".#./..#/###\" of \n",
+    "                Left _ -> M.empty \n",
+    "                Right g -> g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "fromList [((0,0),False),((0,1),True),((0,2),False),((1,0),False),((1,1),False),((1,2),True),((2,0),True),((2,1),True),((2,2),True)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       ".#.\n",
+       "..#\n",
+       "###"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "reflectH g = M.fromList [((r, c) , M.findWithDefault False (rm - r, c) g) | r <- [0..rm], c <- [0..cm] ]\n",
+    "    where (rm, cm) = bounds g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "###\n",
+       "..#\n",
+       ".#."
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ reflectH initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "reflectV g = M.fromList [((r, c) , M.findWithDefault False (r, cm - c) g) | r <- [0..rm], c <- [0..cm] ]\n",
+    "    where (rm, cm) = bounds g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       ".#.\n",
+       "#..\n",
+       "###"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ reflectV initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "transpose g = M.fromList [((c, r) , M.findWithDefault False (r, c) g) | r <- [0..rm], c <- [0..cm] ]\n",
+    "    where (rm, cm) = bounds g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "..#\n",
+       "#.#\n",
+       ".##"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ transpose initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "allArrangements grid = map (\\f -> f grid) [ id\n",
+    "                                          , reflectH\n",
+    "                                          , reflectV\n",
+    "                                          , transpose\n",
+    "                                          , reflectH . transpose\n",
+    "                                          , reflectV . transpose\n",
+    "                                          , reflectH . reflectV . transpose\n",
+    "                                          , reflectV . reflectH\n",
+    "                                          ]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[\".#.\\n..#\\n###\\n\",\"###\\n..#\\n.#.\\n\",\".#.\\n#..\\n###\\n\",\"..#\\n#.#\\n.##\\n\",\".##\\n#.#\\n..#\\n\",\"#..\\n#.#\\n##.\\n\",\"##.\\n#.#\\n#..\\n\",\"###\\n#..\\n.#.\\n\"]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "map showGrid $ allArrangements initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "2"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "sampleRulesCompact = successfulParse \"../.# => ##./#../...\\n.#./..#/### => #..#/..../..../#..#\"\n",
+    "length sampleRulesCompact"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "expandRule rule = [Rule l (rulePost rule) | l <- allArrangements (rulePre rule)]\n",
+    "expandRules = concatMap expandRule"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[\"##.\\n#.#\\n...\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\"...\\n#.#\\n##.\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\".##\\n#.#\\n...\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\"##.\\n#..\\n.#.\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\".#.\\n#..\\n##.\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\".##\\n..#\\n.#.\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\".#.\\n..#\\n.##\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\",\"...\\n#.#\\n.##\\n=>#.#.\\n.#..\\n.##.\\n...#\\n\"]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "[showGrid (rulePre r) ++ \"=>\" ++ showGrid (rulePost r) | r <- expandRule testRule]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "16"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "length $ expandRules sampleRulesCompact"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "readRules = expandRules . successfulParse"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[Rule (fromList [((0,0),False),((0,1),False),((1,0),False),((1,1),True)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),True),((1,0),False),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),False),((1,0),True),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),False),((1,0),False),((1,1),True)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),True),((1,0),False),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),False),((1,0),True),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]) (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),False),((2,2),False)]),Rule (fromList [((0,0),False),((0,1),True),((0,2),False),((1,0),False),((1,1),False),((1,2),True),((2,0),True),((2,1),True),((2,2),True)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),True),((0,1),True),((0,2),True),((1,0),False),((1,1),False),((1,2),True),((2,0),False),((2,1),True),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),False),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),False),((2,0),True),((2,1),True),((2,2),True)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),False),((0,1),False),((0,2),True),((1,0),True),((1,1),False),((1,2),True),((2,0),False),((2,1),True),((2,2),True)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),False),((0,1),True),((0,2),True),((1,0),True),((1,1),False),((1,2),True),((2,0),False),((2,1),False),((2,2),True)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),True),((0,1),False),((0,2),False),((1,0),True),((1,1),False),((1,2),True),((2,0),True),((2,1),True),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),True),((0,1),True),((0,2),False),((1,0),True),((1,1),False),((1,2),True),((2,0),True),((2,1),False),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)]),Rule (fromList [((0,0),True),((0,1),True),((0,2),True),((1,0),True),((1,1),False),((1,2),False),((2,0),False),((2,1),True),((2,2),False)]) (fromList [((0,0),True),((0,1),False),((0,2),False),((0,3),True),((1,0),False),((1,1),False),((1,2),False),((1,3),False),((2,0),False),((2,1),False),((2,2),False),((2,3),False),((3,0),True),((3,1),False),((3,2),False),((3,3),True)])]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "sampleRules = readRules \"../.# => ##./#../...\\n.#./..#/### => #..#/..../..../#..#\"\n",
+    "sampleRules"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 125,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text <- TIO.readFile \"../../data/advent21.txt\"\n",
+    "rules = readRules text"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 126,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "864"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "length rules"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "apply rules grid = rulePost thisRule\n",
+    "    where ri = head $ findIndices (\\r -> rulePre r == grid) rules\n",
+    "          thisRule = rules!!ri"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 37,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "#..#\n",
+       "....\n",
+       "....\n",
+       "#..#"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ apply sampleRules initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "subGrid :: Int -> Grid -> Int -> Int -> Grid\n",
+    "subGrid n g bigR bigC = M.fromList [ ((r, c), \n",
+    "                                      M.findWithDefault False (r + rStep, c + cStep) g) \n",
+    "                                   | r <- [0..(n - 1)], c <- [0..(n - 1)]\n",
+    "                                   ]\n",
+    "    where rStep = bigR * n\n",
+    "          cStep = bigC * n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "explodeGrid' :: Int -> Grid -> ExplodedGrid\n",
+    "explodeGrid' n g = M.fromList [((bigR, bigC), subGrid n g bigR bigC) | bigR <- [0..bigRm], bigC <- [0..bigCm]]\n",
+    "    where (rm, cm) = bounds g\n",
+    "          bigRm = (rm + 1) `div` n - 1\n",
+    "          bigCm = (cm + 1) `div` n - 1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 107,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "explodeGrid :: Grid -> ExplodedGrid\n",
+    "explodeGrid g = if (rm + 1) `rem` 2 == 0 \n",
+    "                then explodeGrid' 2 g\n",
+    "                else explodeGrid' 3 g\n",
+    "    where (rm, cm) = bounds g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 108,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "fromList [((0,0),\"#.\\n..\\n\"),((0,1),\".#\\n..\\n\"),((1,0),\"..\\n#.\\n\"),((1,1),\"..\\n.#\\n\")]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "testEg = explodeGrid $ apply sampleRules initialGrid\n",
+    "M.map showGrid testEg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 77,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "explodedRows eg = [M.filterWithKey (\\(r, _) _ -> r == row) eg | row <- [0..rowMax] ]\n",
+    "    where (rowMax, _) = bounds eg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 78,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[fromList [((0,0),fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]),((0,1),fromList [((0,0),False),((0,1),True),((1,0),False),((1,1),False)])],fromList [((1,0),fromList [((0,0),False),((0,1),False),((1,0),True),((1,1),False)]),((1,1),fromList [((0,0),False),((0,1),False),((1,0),False),((1,1),True)])]]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "explodedRows testEg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 79,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "(>-<) g1 g2 = M.union g1 g2'\n",
+    "    where (_, cm) = bounds g1\n",
+    "          g2' = M.mapKeys (\\(r, c) -> (r, c + cm + 1)) g2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 80,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "(>|<) g1 g2 = M.union g1 g2'\n",
+    "    where (rm, _) = bounds g1\n",
+    "          g2' = M.mapKeys (\\(r, c) -> (r + rm + 1, c)) g2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 81,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "fromList [((0,0),True),((0,1),False),((1,0),False),((1,1),False)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "M.findWithDefault M.empty (0, 0) testEg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 82,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>/* Styles used for the Hoogle display in the pager */\n",
+       ".hoogle-doc {\n",
+       "display: block;\n",
+       "padding-bottom: 1.3em;\n",
+       "padding-left: 0.4em;\n",
+       "}\n",
+       ".hoogle-code {\n",
+       "display: block;\n",
+       "font-family: monospace;\n",
+       "white-space: pre;\n",
+       "}\n",
+       ".hoogle-text {\n",
+       "display: block;\n",
+       "}\n",
+       ".hoogle-name {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-head {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-sub {\n",
+       "display: block;\n",
+       "margin-left: 0.4em;\n",
+       "}\n",
+       ".hoogle-package {\n",
+       "font-weight: bold;\n",
+       "font-style: italic;\n",
+       "}\n",
+       ".hoogle-module {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-class {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".get-type {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "font-family: monospace;\n",
+       "display: block;\n",
+       "white-space: pre-wrap;\n",
+       "}\n",
+       ".show-type {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "font-family: monospace;\n",
+       "margin-left: 1em;\n",
+       "}\n",
+       ".mono {\n",
+       "font-family: monospace;\n",
+       "display: block;\n",
+       "}\n",
+       ".err-msg {\n",
+       "color: red;\n",
+       "font-style: italic;\n",
+       "font-family: monospace;\n",
+       "white-space: pre;\n",
+       "display: block;\n",
+       "}\n",
+       "#unshowable {\n",
+       "color: red;\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".err-msg.in.collapse {\n",
+       "padding-top: 0.7em;\n",
+       "}\n",
+       ".highlight-code {\n",
+       "white-space: pre;\n",
+       "font-family: monospace;\n",
+       "}\n",
+       ".suggestion-warning { \n",
+       "font-weight: bold;\n",
+       "color: rgb(200, 130, 0);\n",
+       "}\n",
+       ".suggestion-error { \n",
+       "font-weight: bold;\n",
+       "color: red;\n",
+       "}\n",
+       ".suggestion-name {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       "</style><div class=\"suggestion-name\" style=\"clear:both;\">Redundant bracket</div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Found:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Why Not:</div><div class=\"highlight-code\" id=\"haskell\">M.findWithDefault M.empty (0, 1) testEg >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-name\" style=\"clear:both;\">Redundant bracket</div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Found:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Why Not:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  M.findWithDefault M.empty (0, 0) testEg</div></div>"
+      ],
+      "text/plain": [
+       "Line 1: Redundant bracket\n",
+       "Found:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)\n",
+       "Why not:\n",
+       "M.findWithDefault M.empty (0, 1) testEg >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)Line 1: Redundant bracket\n",
+       "Found:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)\n",
+       "Why not:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >-<\n",
+       "  M.findWithDefault M.empty (0, 0) testEg"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\".##.\\n....\\n\""
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "showGrid $ (M.findWithDefault M.empty (0, 1) testEg) >-< (M.findWithDefault M.empty (0, 0) testEg)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 83,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>/* Styles used for the Hoogle display in the pager */\n",
+       ".hoogle-doc {\n",
+       "display: block;\n",
+       "padding-bottom: 1.3em;\n",
+       "padding-left: 0.4em;\n",
+       "}\n",
+       ".hoogle-code {\n",
+       "display: block;\n",
+       "font-family: monospace;\n",
+       "white-space: pre;\n",
+       "}\n",
+       ".hoogle-text {\n",
+       "display: block;\n",
+       "}\n",
+       ".hoogle-name {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-head {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-sub {\n",
+       "display: block;\n",
+       "margin-left: 0.4em;\n",
+       "}\n",
+       ".hoogle-package {\n",
+       "font-weight: bold;\n",
+       "font-style: italic;\n",
+       "}\n",
+       ".hoogle-module {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".hoogle-class {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".get-type {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "font-family: monospace;\n",
+       "display: block;\n",
+       "white-space: pre-wrap;\n",
+       "}\n",
+       ".show-type {\n",
+       "color: green;\n",
+       "font-weight: bold;\n",
+       "font-family: monospace;\n",
+       "margin-left: 1em;\n",
+       "}\n",
+       ".mono {\n",
+       "font-family: monospace;\n",
+       "display: block;\n",
+       "}\n",
+       ".err-msg {\n",
+       "color: red;\n",
+       "font-style: italic;\n",
+       "font-family: monospace;\n",
+       "white-space: pre;\n",
+       "display: block;\n",
+       "}\n",
+       "#unshowable {\n",
+       "color: red;\n",
+       "font-weight: bold;\n",
+       "}\n",
+       ".err-msg.in.collapse {\n",
+       "padding-top: 0.7em;\n",
+       "}\n",
+       ".highlight-code {\n",
+       "white-space: pre;\n",
+       "font-family: monospace;\n",
+       "}\n",
+       ".suggestion-warning { \n",
+       "font-weight: bold;\n",
+       "color: rgb(200, 130, 0);\n",
+       "}\n",
+       ".suggestion-error { \n",
+       "font-weight: bold;\n",
+       "color: red;\n",
+       "}\n",
+       ".suggestion-name {\n",
+       "font-weight: bold;\n",
+       "}\n",
+       "</style><div class=\"suggestion-name\" style=\"clear:both;\">Redundant bracket</div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Found:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Why Not:</div><div class=\"highlight-code\" id=\"haskell\">M.findWithDefault M.empty (0, 1) testEg >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-name\" style=\"clear:both;\">Redundant bracket</div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Found:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)</div></div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Why Not:</div><div class=\"highlight-code\" id=\"haskell\">(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  M.findWithDefault M.empty (0, 0) testEg</div></div>"
+      ],
+      "text/plain": [
+       "Line 1: Redundant bracket\n",
+       "Found:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)\n",
+       "Why not:\n",
+       "M.findWithDefault M.empty (0, 1) testEg >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)Line 1: Redundant bracket\n",
+       "Found:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  (M.findWithDefault M.empty (0, 0) testEg)\n",
+       "Why not:\n",
+       "(M.findWithDefault M.empty (0, 1) testEg) >|<\n",
+       "  M.findWithDefault M.empty (0, 0) testEg"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\".#\\n..\\n#.\\n..\\n\""
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "showGrid $ (M.findWithDefault M.empty (0, 1) testEg) >|< (M.findWithDefault M.empty (0, 0) testEg)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 88,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "contractExploded :: ExplodedGrid -> Grid\n",
+    "contractExploded gs = foldl1 (>|<) $ map (foldl1 (>-<)) rows\n",
+    "    where rows = explodedRows gs"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 90,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"#..#\\n....\\n....\\n#..#\\n\""
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "showGrid $ contractExploded testEg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 94,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "##.##.\n",
+       "#..#..\n",
+       "......\n",
+       "##.##.\n",
+       "#..#..\n",
+       "......"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ contractExploded $ M.map (apply sampleRules) testEg"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 128,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "applyOnce rules g = contractExploded $ M.map (apply rules) $ explodeGrid g"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 135,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       ".#.#.#.#..##..#.##\n",
+       ".#.#...#..##....##\n",
+       "..####..##..####..\n",
+       "..#.#..##.###.#.#.\n",
+       "....#..##.###...#.\n",
+       "###..##..#..###..#\n",
+       "..#.##..#.##.##...\n",
+       "....##....##.##.##\n",
+       "####..####..#..##.\n",
+       "#.#.#.#.#.#.....#.\n",
+       "#...#.#...#..##.#.\n",
+       "###..####..###...#\n",
+       "..#.##..#.##..#.##\n",
+       "....##....##....##\n",
+       "####..####..####..\n",
+       "#.#.#.#.#.#.#.#.#.\n",
+       "#...#.#...#.#...#.\n",
+       "###..####..####..#"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ (!! 5) $ iterate (applyOnce rules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 131,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       ".#\n",
+       "##"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ (!(1, 2)) $ explodeGrid $ last $ take 3 $ iterate (applyOnce rules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 136,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "countLit = M.size . M.filter id"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 137,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "countLit initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 142,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "##.##.\n",
+       "#..#..\n",
+       "......\n",
+       "##.##.\n",
+       "#..#..\n",
+       "......"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "putStrLn $ showGrid $ (!! 2) $ iterate (applyOnce sampleRules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 143,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "12"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "countLit $ (!! 2) $ iterate (applyOnce sampleRules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 144,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "158"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "countLit $ (!! 5) $ iterate (applyOnce rules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 145,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "2301762"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "countLit $ (!! 18) $ iterate (applyOnce rules) initialGrid"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Haskell",
+   "language": "haskell",
+   "name": "haskell"
+  },
+  "language_info": {
+   "codemirror_mode": "ihaskell",
+   "file_extension": ".hs",
+   "name": "haskell",
+   "version": "8.0.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}