To test this in ghci:
import Percolation > percolated $ addSites [(2,2),(3,3),(0,1),(0,2),(0,3),(0,4)] $ newGrid 5 False > percolated $ addSites [(2,2),(3,3),(0,1),(0,2),(0,3),(0,4),(0,0)] $ newGrid 5 True
To test this in ghci:
import Percolation > percolated $ addSites [(2,2),(3,3),(0,1),(0,2),(0,3),(0,4)] $ newGrid 5 False > percolated $ addSites [(2,2),(3,3),(0,1),(0,2),(0,3),(0,4),(0,0)] $ newGrid 5 True
module Percolation where | |
import Control.Monad (join) | |
import Data.Maybe (mapMaybe) | |
import qualified Data.Map.Lazy as M | |
import qualified Data.Set as S | |
-- | Percolation algorithm. | |
-- | |
-- Important assumptions or implementation details: | |
-- | |
-- * When sets are unioned, the lowest SetId is used as | |
-- the SetId for the new set. This allows us to check | |
-- whether we have percolated by asking whether Set 1 | |
-- (initially the bottom) has been removed due to merging | |
-- with Set 0 (initially the top). | |
type SiteId = Int | |
type SetId = Int | |
type Coord = (Int,Int) | |
data Grid = | |
Grid | |
Int | |
-- ^ Dimension of matrix | |
SiteId | |
-- ^ Next SiteId to allocate | |
(M.Map (Int,Int) (Maybe SiteId)) | |
-- ^ Mapping of coordinate to SiteId | |
(M.Map SiteId SetId) | |
-- ^ Mapping of SiteId to SetId | |
(S.Set SetId) | |
-- ^ Mapping of SetId to set of connected coordinates | |
deriving (Show) | |
newGrid :: Int -> Grid | |
newGrid n = | |
let | |
coords = [ (x,y) | x <- [0..n-1], y <- [0..n-1] ] | |
topCoords = [ (x,-1) | x <- [0..n-1] ] | |
bottomCoords = [ (x,n) | x <- [0..n-1] ] | |
coordMap = foldr (`M.insert` Nothing) M.empty coords | |
coordMap' = foldr (`M.insert` Just 0) coordMap topCoords | |
coordMap'' = foldr (`M.insert` Just 1) coordMap' bottomCoords | |
in | |
Grid n 2 coordMap'' | |
(M.fromList [(0,0),(1,1)]) | |
(S.fromList [0, 1]) | |
percolated :: Grid -> Bool | |
percolated (Grid _ _ _ _ sets) = S.notMember 1 sets | |
addSite :: Coord -> Grid -> Grid | |
addSite xy@(x,y) g@(Grid n nextSiteId siteIds setIds sets) = | |
case M.lookup xy siteIds of | |
Just Nothing -> | |
-- site not open; find surrounding setIds | |
let | |
adjSiteIds = mapMaybe (join . (`M.lookup` siteIds)) | |
[(x,y-1),(x-1,y),(x+1,y),(x,y+1)] | |
adjSetIds = mapMaybe (`M.lookup` setIds) adjSiteIds | |
siteId = foldr min nextSiteId adjSiteIds | |
setId = foldr min nextSiteId adjSetIds | |
in | |
Grid n | |
(if setId == nextSiteId then nextSiteId + 1 else nextSiteId) | |
(M.insert xy (Just siteId) siteIds) | |
(foldr (`M.insert` setId) setIds (setId : adjSetIds)) | |
(S.insert setId | |
$ foldr S.delete sets adjSetIds) | |
_ -> | |
-- site out of bounds, or already open; return grid unchanged | |
g | |
addSites :: [Coord] -> Grid -> Grid | |
addSites = foldr ((.) . addSite) id |