Main.hs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. module Main where
  2. import Numeric (showHex, showIntAtBase)
  3. import Data.List (isInfixOf, genericIndex)
  4. import Data.List.Split(splitOn)
  5. import Text.Read (readMaybe)
  6. import System.IO (hFlush, stdout)
  7. import Control.Parallel
  8. import Options.Applicative
  9. import Data.Semigroup ((<>))
  10. -- Get the hex digit of Pi at place n.
  11. hexPi :: Integer -> Integer
  12. hexPi n =
  13. let
  14. summation = let
  15. sone = (4 * sumPi n 1)
  16. sfour = (2 * sumPi n 4)
  17. sfive = (sumPi n 5)
  18. ssix = (sumPi n 6)
  19. in
  20. sone `par` sfour `par` sfive `par` ssix `par` sone - sfour - sfive - ssix
  21. skimmedSum = summation - (fromIntegral (floor summation :: Integer)) -- Take only the decimal portion
  22. in
  23. floor (16 * skimmedSum) :: Integer
  24. -- Calculate the summation.
  25. -- 5000 is used in place of Infinity. This value drops off quickly, so no need to go too far.
  26. sumPi :: Integer -> Integer -> Double
  27. sumPi n x =
  28. let
  29. summation1 = sum [(fromIntegral ((16^(n-k) `mod` ((8*k)+x)))) / (fromIntegral ((8*k)+x)) | k <- [0..n]]
  30. summation2 = sum [16^^(n-k) / (fromIntegral ((8*k)+x)) | k <- [(n+1)..5000]]
  31. in
  32. summation1 + summation2
  33. -- Get a range of digits.
  34. rangePi :: (Integer -> String) -> String -> Maybe Integer -> Maybe Integer -> String
  35. rangePi printFun delim (Just low) (Just high) =
  36. if low >= high then
  37. "Error: Please give a proper range."
  38. else
  39. init $ foldr (++) [] $ separate (map printFun (drop (fromIntegral low) . take (fromIntegral high) $ hexDigits)) delim
  40. -- foldl (.) id (map (showString . printFun) (drop (fromIntegral low) . take (fromIntegral high) $ hexDigits)) "" -- Alternative implementation: not sure about speed
  41. rangePi _ _ _ _ = printErr
  42. -- Separate a list with some delimiter.
  43. separate :: [a] -> a -> [a]
  44. separate (x:xs) delim = x : delim : separate xs delim
  45. separate [] _ = []
  46. -- Check if response is valid, then call the appropriate function.
  47. parseIndex :: (Integer -> String) -> String -> String -> String
  48. parseIndex printFun delim response =
  49. if (isInfixOf ".." response) then
  50. let
  51. range = splitOn ".." response
  52. low = readMaybe $ range !! 0 :: Maybe Integer
  53. high = readMaybe $ range !! 1 :: Maybe Integer
  54. in
  55. rangePi printFun delim low high
  56. else do
  57. case (readMaybe response :: Maybe Integer) of
  58. Nothing -> printErr
  59. Just x -> printFun $ hexDigits `genericIndex` x
  60. -- Continuously prompt for input.
  61. prompt :: (Integer -> String) -> String -> IO ()
  62. prompt printFun delim = do
  63. putStr ">> "
  64. hFlush stdout
  65. response <- getLine
  66. putStrLn $ parseIndex printFun delim response
  67. prompt printFun delim
  68. -- The list of answers.
  69. hexDigits :: [Integer]
  70. hexDigits = [hexPi x | x <- [0..]]
  71. -- Complain about argument type.
  72. printErr :: String
  73. printErr = "Error: Please give an Integer (ex: 3) or a range (ex: 3..5)."
  74. main :: IO ()
  75. main = argHandle =<< execParser opts
  76. where
  77. opts = info (arguments <**> helper)
  78. ( fullDesc
  79. <> progDesc "Generate hexadecimal Pi digits.")
  80. -- Arguments to parse.
  81. data Arguments = Arguments
  82. { eval :: Maybe String
  83. , print :: Maybe PrintFunc
  84. , delimiter :: String}
  85. arguments :: Parser Arguments
  86. arguments = Arguments
  87. <$> optional ( argument str (
  88. help "Evaluate an index/range, and exit."
  89. <> (metavar "eval")))
  90. <*> printFunc
  91. <*> strOption ( long "delimiter"
  92. <> metavar "delim"
  93. <> value ""
  94. <> help "Delimiter to separate printed values.")
  95. -- Decide which printing function to use.
  96. data PrintFunc = DecPrint | BinPrint
  97. printFunc :: Parser (Maybe PrintFunc)
  98. printFunc = optional (decPrint <|> binPrint)
  99. decPrint :: Parser PrintFunc
  100. decPrint = flag' DecPrint ( long "decimal"
  101. <> short 'd'
  102. <> help "Output in decimal.")
  103. binPrint :: Parser PrintFunc
  104. binPrint = flag' BinPrint ( long "binary"
  105. <> short 'b'
  106. <> help "Output in binary.")
  107. -- Handle args, either prompt or eval & quit.
  108. argHandle :: Arguments -> IO ()
  109. argHandle (Arguments toEval outputType delim) = do
  110. let
  111. printFunIO =
  112. case outputType of
  113. Just DecPrint -> do
  114. putStrLn "Outputting in decimal."
  115. return (\n -> show n ++ "")
  116. Just BinPrint -> do
  117. putStrLn "Outputting in binary."
  118. return (\n -> showIntAtBase 2 (\x -> show x !! 0) n "")
  119. _ -> do
  120. putStrLn "Outputting in hex."
  121. return (\n -> showHex n "")
  122. in do
  123. printFun <- printFunIO
  124. case toEval of
  125. (Just s) -> putStrLn $ parseIndex printFun delim s
  126. _ -> do
  127. putStrLn "Enter a digit or range (Ctrl-C to exit)."
  128. prompt printFun delim