packages feed

detrospector-0.2: Detrospector/Modes/Train.hs

{-# LANGUAGE
    NamedFieldPuns
  , BangPatterns  #-}
module Detrospector.Modes.Train(train) where

import Detrospector.Types
import Detrospector.Modes

import System.IO
import qualified Data.Text           as TS
import qualified Data.Text.Lazy      as TL
import qualified Data.Text.Lazy.IO   as TL
import qualified Data.HashMap.Strict as H
import qualified Data.IntMap         as IM
import qualified Data.Sequence       as S
import qualified Data.Foldable       as F

-- foldl' with progress dots
progFold :: (a -> b -> a) -> a -> [b] -> IO a
progFold f = go where
  go !v []     = return v
  go !v (x:xs) = putChar '.' >> go (f v x) xs

-- The guts of a Chain, before we apply 'cumulate'.
type FreqChain = H.HashMap (S.Seq Char) FreqTable

-- Build a Markov chain with n-Char history from some input text.
train :: ModeFun
train Train{num,out} = do
  hSetBuffering stdout NoBuffering
  ys <- TL.getContents
  putStr "Calculating"
  (_,h) <- progFold (TS.foldl' roll) (emptyQ,H.empty) $ TL.toChunks ys
  putStrLn "done."
  writeChain out . Chain num $ H.map cumulate h where

  -- Process another character, updating a fold state of the Markov chain
  -- history and the accumulated FreqChain.
  roll :: (S.Seq Char, FreqChain) -> Char -> (S.Seq Char, FreqChain)
  roll (!s, !fci) x = (shift num x s, F.foldr occur fci $ S.tails s) where

    -- Increment the occurrence count for 'x' following history 'hist'.
    occur :: S.Seq Char -> FreqChain -> FreqChain
    occur hist fc = H.insert hist (bump $ H.lookup hist fc) fc

    -- Given a FreqTable (or Nothing), return a FreqTable with one more
    -- count for 'x'.
    bump :: Maybe FreqTable -> FreqTable
    bump Nothing  = IM.singleton (fromEnum x) 1
    bump (Just v) = IM.alter incMaybe (fromEnum x) v

    incMaybe :: Maybe Int -> Maybe Int
    incMaybe Nothing  = Just 1
    incMaybe (Just n) = Just $! (n+1)

train _ = error "impossible: wrong mode passed to train"