packages feed

detrospector-0.2: Detrospector/Types.hs

{-# LANGUAGE
    ViewPatterns
  , PatternGuards #-}
{-# OPTIONS_GHC
  -fno-warn-orphans #-}
module Detrospector.Types(
    FreqTable
  , PickTable
  , cumulate
  , RNG
  , Queue, emptyQ, listToQueue, queueToList, qLength, qSnoc
  , shift
  , Chain(..), pick
  , withChain, writeChain) where

import Data.Maybe
import Data.List
import Control.Applicative
import qualified Data.HashMap.Strict    as H
import qualified Data.Hashable          as H
import qualified Data.IntMap            as IM
import qualified System.Random.MWC      as RNG
import qualified Data.Sequence          as S
import qualified Data.Foldable          as F
import qualified Data.Binary            as Bin
import qualified Data.ByteString.Lazy   as BSL
import qualified Codec.Compression.GZip as Z

-- This table records character frequency;
-- each Int key is the codepoint of a Char.
type FreqTable = IM.IntMap Int

-- A table for efficiently sampling a finite
-- discrete distribution of Char.
--
-- To sample from (PickTable n im):
-- * Pick random k from [0,n) uniformly
-- * Take value of first key > k
data PickTable = PickTable Int (IM.IntMap Char)
  deriving (Show)

cumulate :: FreqTable -> PickTable
cumulate t = PickTable r $ IM.fromList ps where
  (r,ps) = mapAccumR f 0 $ IM.assocs t
  f ra (x,n) = let rb = ra+n in (rb, (rb, toEnum x))

type RNG = RNG.GenIO

-- Sample from a PickTable.
sample :: PickTable -> RNG -> IO Char
sample (PickTable t im) g = do
  k <- (`mod` t) <$> RNG.uniform g
  case IM.split k im of
    -- last key in im is t, and we know k < t
    -- therefore the right list cannot be empty
    (_, IM.toList -> ((_,x):_)) -> return x
    _ -> error "impossible"

type Queue a = S.Seq a

emptyQ :: Queue a
emptyQ = S.empty

listToQueue :: [a] -> Queue a
listToQueue = S.fromList

queueToList :: Queue a -> [a]
queueToList = F.toList

qLength :: Queue a -> Int
qLength = S.length

qSnoc :: Queue a -> a -> Queue a
qSnoc = (S.|>)

-- Enqueue at one side while dropping from the other
shift :: Int -> a -> Queue a -> Queue a
shift n x q
  | S.length q < n          = q S.|> x
  | (_ S.:< s) <- S.viewl q = s S.|> x
  | otherwise               = q S.|> x

-- The Markov chain itself.
-- (Chain n hm) maps subsequences of up to n Chars to finite
-- Char distributions represented by PickTables in hm.
data Chain = Chain Int (H.HashMap (Queue Char) PickTable)
  deriving (Show)

-- Pick from a chain according to history.  Returns a character
-- and the amount of history actually used.
pick :: Chain -> Queue Char -> RNG -> IO (Char, Int)
pick (Chain _ h) s g = do x <- sample pt g; return (x,hn) where
  (pt, hn) = get s

  -- assumption: map is nonempty for empty key
  get  t = fromMaybe (get $ qTail t) look where
    qTail (S.viewl -> (_ S.:< r)) = r
    qTail _ = error "qTail: empty queue"
    look = do x <- H.lookup t h; return (x, S.length t)

-- orphan instance: make Seq hashable
instance (H.Hashable a) => H.Hashable (S.Seq a) where
  {-# SPECIALIZE instance H.Hashable (S.Seq Char) #-}
  hash = F.foldl' (\acc h -> acc `H.combine` H.hash h) 0

-- orphan instance: Binary serialization of HashMap
instance (Bin.Binary k, Bin.Binary v, H.Hashable k, Ord k)
       => Bin.Binary (H.HashMap k v) where
  put = Bin.put . H.toList
  get = H.fromList <$> Bin.get
  
instance Bin.Binary PickTable where
  put (PickTable n t) = Bin.put (n,t)
  get = uncurry PickTable <$> Bin.get

instance Bin.Binary Chain where
  put (Chain n h) = Bin.put (n,h)
  get = uncurry Chain <$> Bin.get

withChain :: FilePath -> (Chain -> RNG -> IO a) -> IO a
withChain p f = do
  ch <- (Bin.decode . Z.decompress) <$> BSL.readFile p
  RNG.withSystemRandom $ f ch

writeChain :: FilePath -> Chain -> IO ()
writeChain out = BSL.writeFile out . Z.compress . Bin.encode