繁体   English   中英

在Haskell中使用IO Bool进行列表理解

[英]List comprehension with IO Bool in Haskell

我正在尝试通过Miller Rabin素数检查来检查Mersenne数,以找到可能的Mersenne素数。

如果mersennes是Mersenne数的无限列表,我想做些类似的事情:

probableMersennePrimes :: IO [Integer]
probableMersennePrimes = [ n | n <- mersennes, primeMillerRabin n ]

其中primeMillerRabin :: Integer -> IO Bool

作为附带问题,类型应该是IO [Integer]还是[IO Integer]

您可以使用pipes ListT流式传输数字的无限列表:

import Control.Monad
import Data.Monoid
import Pipes

mersennes :: [Integer]
mersennes = undefined

primeMillerRabin :: Integer -> IO Bool
primeMillerRabin = undefined

probableMersennePrimes :: ListT IO Integer
probableMersennePrimes = do
    n        <- Select (each mersennes)
    continue <- lift (primeMillerRabin n)
    guard continue
    return n

main = runListT $ do
    n <- probableMersennePrimes
    lift (print n)
    mempty

即使mersennes列表是无限的,它也可以工作,并且在您计算结果时将流式传输结果。

如果将primeMillerRabin更改为RandomGen g => Integer -> State g Bool ,则可以使用filterM进行此filterM

probableMersennePrimes :: RandomGen g => g -> [Integer]
probableMersennePrimes = evalState $ filterM primeMillerRabin mersennes

通过使用evalState ,我们放弃了filterM primeMillerRabin mersennes计算的最终状态,因此我们对此并不严格。 但这是一件好事,因为最终状态只有在到达进程的mersennes列表的末尾mersennes ,该列表是无限的,并且没有结束。

这允许计算延迟生成probableMersennePrimes gen元素。

由于随机数生成器的工作原理,您无法在返回IO操作时执行此操作。 IO计算需要知道结束状态是什么,以便可以在计算之后生成另一个随机数,因此它必须无限循环,以寻找无限列表的末尾。

但是,不要只是相信我,可以尝试一下:

module SO26307073 where
import Control.Monad.State
import System.Random

-- find how many times a factor divides a number
-- (p^s * d) `factorBy` p == (s,d) iff d `rem` p /= 0
factorBy :: Integral a => a -> a -> (Int,a)
factorBy n p = (length steps - 1, fst $ last steps)
  where steps = takeWhile ((==0) . snd) $ iterate (flip quotRem 2 . fst) (n, 0)

mersennes :: Num a => [a]
mersennes = [ 2^n - 1 | n <- [2..] ]

type RandomRM m = (Integer, Integer) -> m Integer

primeMillerRabinWith :: Monad m => RandomRM m ->  Integer -> m Bool
primeMillerRabinWith randomRM n = do
  let nMinus1 = n-1
      (s,d) = nMinus1 `factorBy` 2
  liftM (all id) . replicateM 10 $ do
    a <- randomRM (2, nMinus1)
    let x = (a^d) `mod` n
    let xs = take s $ iterate (\x -> (x^2) `mod` n) x
    return $ x == 1 || any (== nMinus1) xs

probableMersennePrimesWith :: Monad m => RandomRM m -> m [Integer]
probableMersennePrimesWith randomRM = filterM (primeMillerRabinWith randomRM) mersennes

probableMersennePrimesPure :: RandomGen g => g -> [Integer]
probableMersennePrimesPure = evalState . probableMersennePrimesWith $ state . randomR

probableMersennePrimesIO :: IO [Integer]
probableMersennePrimesIO = probableMersennePrimesWith $ randomRIO

请注意, probableMersennePrimesIOprobableMersennePrimesPure仅使用不同的方式绘制随机数。

弹出ghci,我们可以看到纯版本可以正常工作,而IO版本只是挂起:

λ import Control.Applicative
λ import System.Random
λ :l SO26307073
[1 of 1] Compiling SO26307073      ( SO26307073.hs, interpreted )
Ok, modules loaded: SO26307073.
λ take 0 . probableMersennePrimesPure <$> newStdGen
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package old-locale-1.0.0.6 ... linking ... done.
Loading package time-1.4.2 ... linking ... done.
Loading package random-1.0.1.1 ... linking ... done.
Loading package transformers-0.4.1.0 ... linking ... done.
Loading package mtl-2.2.1 ... linking ... done.
[]
λ take 5 . probableMersennePrimesPure <$> newStdGen
[3,7,31,127,8191]
λ take 5 <$> probableMersennePrimesIO 
^CInterrupted.

所有其他答案都很好。 但是,我认为解决此问题的最简单方法是惰性IO,值得考虑。 我还认为,在这种特殊情况下,由于不涉及系统资源(文件句柄等),因此惰性IO是无害的。

您只需要为惰性IO重新定义特殊的filterM

import System.IO.Unsafe (unsafeInterleaveIO)

filterMIO :: (a -> IO Bool) -> [a] -> IO [a]
filterMIO p = go
  where
    go []     = return []
    go (x:xs) = do
      xs' <- unsafeInterleaveIO (go xs)
      b   <- p x
      return $ if b then (x:xs') else xs'

probableMersennePrimes :: IO [Integer]
probableMersennePrimes = filteMIO primeMillerRabin mersennes

请注意,这将对无限列表有效。 这就是为什么我们实际上使用了惰性IO的原因!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM