import System.Random
import qualified Data.Set as S
import Text.Printf

findRep :: (Random a, Integral a, RandomGen b) => a -> b -> (a, b)
findRep n gen = findRep' (S.singleton 1) 1 gen
    where
      findRep' seen len gen'
          | S.member fx seen = (len, gen'')
          | otherwise        = findRep' (S.insert fx seen) (len + 1) gen''
          where
            (fx, gen'') = randomR (1, n) gen'

statistical:: (Integral a, Random b, Integral b, RandomGen c, Fractional d) =>
               a -> b -> c -> (d, c)
statistical samples size gen =
    let (total, gen') = sar samples gen 0
    in ((fromIntegral total) / (fromIntegral samples), gen')
    where
      sar 0        gen' acc = (acc, gen')
      sar samples' gen' acc =
          let (len, gen'') = findRep size gen'
          in sar (samples' - 1) gen'' (acc + len)

factorial:: (Integral a) => a -> a
factorial n = foldl (*) 1 [1..n]

analytical:: (Integral a, Fractional b) => a -> b
analytical n = sum [fromIntegral num /
                    fromIntegral (factorial (n - i)) /
                    fromIntegral (n ^ i) |
                    i <- [1..n]]
    where num = factorial n

test:: (Integral a, Random b, Integral b, PrintfArg b, RandomGen c) =>
        a -> [b] -> c -> IO c
test _       []     gen = return gen
test samples (x:xs) gen = do
  let (st, gen') = statistical samples x gen
      an         = analytical x
      err        = abs (st - an) / st * 100.0
      str        = printf "%3d %9.4f %12.4f  (%6.2f%%)\n"
                   x (st :: Float) (an :: Float) (err :: Float)
  putStr str
  test samples xs gen'

main:: IO ()
main = do
  putStrLn " N    average    analytical    (error)"
  putStrLn "===  =========  ============  ========="
  let samples = 10000:: Integer
      range   = [1..20]:: [Integer]
  _ <- test samples range $ mkStdGen 0
  return ()