content: import System.Random
  import Control.Monad
  import Data.List
  import Data.Ord
  import Data.Array
  
  showNum :: (Num a, Show a) => Int -> a -> String
  showNum w = until ((>w-1).length) (' ':) . show
  
  replace :: Int -> a -> [a] -> [a]
  replace n c ls = take (n-1) ls ++ [c] ++ drop n ls
  
  target = "METHINKS IT IS LIKE A WEASEL"
  pfit = length target
  mutateRate = 20
  popsize = 100
  charSet = listArray (0,26) $ ' ': ['A'..'Z'] :: Array Int Char
  
  fitness = length . filter id . zipWith (==) target
  
  printRes i g = putStrLn $
       "gen:" ++ showNum 4 i ++ "  "
       ++ "fitn:" ++ showNum 4  (round $ 100 * fromIntegral s / fromIntegral pfit ) ++ "%  "
       ++ show g
      where s = fitness g
  
  mutate :: [Char] -> Int -> IO [Char]
  mutate g mr = do
    let r = length g
    chances <- replicateM r $ randomRIO (1,mr)
    let pos = elemIndices 1 chances
    chrs <- replicateM (length pos) $ randomRIO (bounds charSet)
    let nchrs = map (charSet!) chrs
    return $ foldl (\ng (p,c) -> replace (p+1) c ng) g (zip pos nchrs)
  
  evolve :: [Char] -> Int -> Int -> IO ()
  evolve parent gen mr = do
    when ((gen-1) `mod` 20 == 0) $ printRes (gen-1) parent
    children <- replicateM popsize (mutate parent mr)
    let child = maximumBy (comparing fitness) (parent:children)
    if fitness child == pfit then printRes gen child
                             else evolve child (succ gen) mr
  
  main = do
    let r = length target
    genes <- replicateM r $ randomRIO (bounds charSet)
    let parent = map (charSet!) genes
    evolve parent 1 mutateRate
  