reanimate-0.4.2.0: examples/sorting.hs
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PackageImports #-}
module Main (main) where
import Codec.Picture
import Control.Monad.ST
import Control.Monad.State.Strict
import Data.Text (Text)
import qualified Data.Vector.Generic.Mutable as GV
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V
import Reanimate
import System.Random
import "random-shuffle" System.Random.Shuffle
main :: IO ()
main = reanimate $
demonstrateAlgorithm "Bubble sort" bubbleSort `seqA`
demonstrateAlgorithm "Merge sort (left leaning)" mergeSort `seqA`
demonstrateAlgorithm "Merge sort" mergeSortUp `seqA`
demonstrateAlgorithm "Insertion sort" insertSort `seqA`
demonstrateAlgorithm "Selection sort" selectionSort `seqA`
adjustDuration (*3) (demonstrateAlgorithm "Quicksort" quicksort)
demonstrateAlgorithm :: Text -> (forall s. S s ()) -> Animation
demonstrateAlgorithm name algo = mkAnimation 10 $ \t ->
let img = generateImage pixelRenderer width height
seed = round (t * 3000)
pixelRenderer x y = turbo (fromIntegral num / fromIntegral width)
where
num = (sortedDat !! y) V.! x
sortedDat = runSort' seed algo width
-- width = 1024
width = 500
height = length sortedDat
in mkGroup
[ mkBackground "black"
, translate 0 (-screenWidth*0.03) $ center $ scaleXY (-1) 1 $
scaleToSize 7.5 7.5 $ embedImage img
, translate 0 (screenWidth*0.24) $ withFillColor "white" $ scale 1 $ center $
latex name
, withFillColor "white" $
translate (-screenWidth*0.26) (-screenHeight*0.05) $
rotate (-90) $ scale 0.5 $ center $
latex "$Time \\rightarrow$"
, withFillColor "white" $ translate (screenWidth*0.30) 0 $
mkCircle ((1-t)*0.5)
]
-- main :: IO ()
-- main = print $ length $ runSort bubbleSort 2560
-- [3,4,1,2]
-- [
-- [1,2,3,4]
-- 0 3
-- half: 0 + 3`div`2 = 1
-- mergeSort 0 (0+1)
-- mergeSort (0+1) 3
data Env s = Env
{ envHistory :: [Vector Int]
, envState :: V.MVector s Int }
type S s a = StateT (Env s) (ST s) a
-- runSort :: (forall s. S s ()) -> Int -> [Vector Int]
-- runSort = runSort' 0xDEADBEEF
runSort' :: Int -> (forall s. S s ()) -> Int -> [Vector Int]
runSort' seed sortFn len = reverse $ runST (do
arr <- V.thaw (V.fromList lst)
let env = Env [] arr
envHistory <$> execStateT sortFn env)
where
lst = shuffle' [1 .. len] len (mkStdGen seed)
readS :: Int -> S s Int
readS idx = do
arr <- gets envState
GV.unsafeRead arr idx
writeS :: Int -> Int -> S s ()
writeS idx val = do
arr <- gets envState
GV.unsafeWrite arr idx val
swapS :: Int -> Int -> S s ()
swapS a b = do
arr <- gets envState
GV.unsafeSwap arr a b
inputLength :: S s Int
inputLength = GV.length <$> gets envState
snapshot :: S s ()
snapshot = do
arr <- gets envState
vec <- V.freeze arr
modify $ \st -> st { envHistory = vec : envHistory st }
mergeSort :: S s ()
mergeSort = do
snapshot
len <- inputLength
mergeSort' 0 (len-1)
mergeSort' :: Int -> Int -> S s ()
mergeSort' start end | start == end = return ()
mergeSort' start end = do
let half = start + (end-start) `div` 2
mergeSort' start half
mergeSort' (half+1) end
leftVals <- mapM readS [start .. half]
rightVals <- mapM readS [half+1 .. end]
zipWithM_ writeS [start..] (merge leftVals rightVals)
snapshot
merge :: Ord a => [a] -> [a] -> [a]
merge [] xs = xs
merge xs [] = xs
merge (x:xs) (y:ys)
| x < y = x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys
mergeSortUp :: S s ()
mergeSortUp = do
snapshot
len <- inputLength
let chunkSizes = takeWhile (< len) $ map (2^) [0::Int ..]
forM_ chunkSizes $ bottomUpMergeSort'
bottomUpMergeSort' :: Int -> S s ()
bottomUpMergeSort' chunkSize = do
len <- inputLength
forM_ [0, chunkSize*2 .. len-1] $ \idx -> do
leftVals <- mapM readS (take chunkSize [idx .. len-1])
rightVals <- mapM readS (take chunkSize (drop chunkSize [idx .. len-1]))
zipWithM_ writeS [idx..] (merge leftVals rightVals)
snapshot
selectionSort :: S s ()
selectionSort = do
snapshot
len <- inputLength
forM_ [0 .. len-1] $ \j -> do
i <- findMin j (j+1) len
swapS j i
snapshot
where
findMin j i len | i >= len = return j
findMin j i len = do
jVal <- readS j
iVal <- readS i
if iVal < jVal
then findMin i (i+1) len
else findMin j (i+1) len
insertSort :: S s ()
insertSort = do
snapshot
len <- inputLength
forM_ [1 .. len-1] $ \j -> do
a <- readS j
worker a j
snapshot
where
worker a 0 = writeS 0 a
worker a j = do
b <- readS (j-1)
if a < b
then do
writeS j b
worker a (j-1)
else
writeS j a
bubbleSort :: S s ()
bubbleSort = do
worker True 0
where
worker True 0 = do
snapshot
len <- inputLength
worker False (len-1)
worker False 0 = snapshot
worker changed n = do
a <- readS n
b <- readS (n-1)
if a < b
then do
writeS n b
writeS (n-1) a
when (n `mod` 50 == 0) snapshot
worker True (n-1)
else worker changed (n-1)
quicksort :: S s ()
quicksort = do
snapshot
len <- inputLength
worker [(0, len-1)]
where
worker :: [(Int,Int)] -> S s ()
worker [] = return ()
worker ((lo,hi):rest) = do
pivot <- readS (lo + (hi-lo) `div` 2)
p <- partition pivot lo hi
snapshot
worker $ insertWork (lo, p) $ insertWork (p+1, hi) $ rest
partition pivot lo hi = do
loVal <- readS lo
hiVal <- readS hi
if loVal < pivot
then partition pivot (lo+1) hi
else if hiVal > pivot
then partition pivot lo (hi-1)
else if lo >= hi
then return hi
else do
writeS lo hiVal
writeS hi loVal
snapshot
partition pivot (lo+1) (hi-1)
insertWork :: (Int,Int) -> [(Int,Int)] -> [(Int,Int)]
insertWork (lo, hi) rest | lo >= hi = rest
insertWork (lo, hi) [] = [(lo, hi)]
insertWork (lo, hi) ((lo', hi'):rest)
| hi-lo > hi'-lo' = (lo,hi) : (lo', hi') : rest
| otherwise = (lo', hi') : insertWork (lo, hi) rest