【问题标题】:Haskell prime testHaskell 素数测试
【发布时间】:2011-05-31 07:07:04
【问题描述】:

我是 Haskell 的新手,我正在尝试一下:

isPrime :: Integer->Bool
isPrime x = ([] == [y | y<-[2..floor (sqrt x)], mod x y == 0])

我有几个问题。

  1. 为什么当我尝试加载 .hs 时,WinHugs 说:(Floating Integer, RealFrac Integer) 的实例需要isPrime 的定义?
  2. 当解释器在正确的集合中找到一个元素时,它会立即停止还是计算所有集合?我想你知道我的意思。

对不起我的英语。

【问题讨论】:

    标签: haskell primes short-circuiting


    【解决方案1】:
    1. 因为sqrt 的类型为Floating a =&gt; a -&gt; a。这意味着输入必须是 Floating 类型,并且输出将是相同的类型。换句话说,x 必须是 Floating 类型。但是,您将 x 声明为 Integer 类型,这不是 Floating 类型。另外floor 需要RealFrac 类型,所以x 也需要这样。

      错误消息建议您通过将Integer 设为Floating 类型(通过定义实例Floating IntegerRealFrac 也是如此)来解决此问题。

      当然,在这种情况下这不是正确的方法。相反,您应该使用fromIntegralx 转换为Real(这是FloatingRealFrac 的一个实例),然后将其提供给sqrt

    2. 是的。一旦== 看到右操作数至少有一个元素,它就知道它不等于[],因此返回False

      话虽如此,null 是一种比[] == 更惯用的检查列表是否为空的方法。

    【讨论】:

    • 至于惯用的解决方案,我建议truncate . sqrt . fromIntegral 否。 1 和all (\y -&gt; x `mod` y /= 0) [...].
    【解决方案2】:

    关于第二点,停止,例如:

    [] == [x | x <- [1..]]
    

    返回False

    【讨论】:

    • [x | x &lt;- [1..]][1..] 顺便说一句。
    【解决方案3】:
    1. 我认为 WinHugs 需要为 Integer 等导入一个模块……试试 Int
    2. 在您调用之前,解释器不会计算任何内容,例如isPrime 32 然后它会懒惰地计算表达式。

    PS 你的 isPrime 实现不是最好的实现!

    【讨论】:

    • 1) Integer 的“import[ing] a module”不是问题;问题是根据他的定义,“Instances of (Floating Integer, RealFrac Integer) required for definition of isPrime”,就像 WinHugs 所说的那样。 2) 是的,而且...?这太无关紧要了,我不知道如何回应;首先,修复定义使其工作,然后担心如何使用它。 PS)OP的isPrime实现不是最好的,直觉是“他”实现的!帮助解释如何解决他的问题,写出你自己的,“或 GTFO!”
    • 1) 你说得对,我已经很久没有使用 Haskell 了。 2)我试图让他知道haskell的懒惰可能太微不足道了PS)他听起来像个学生,没有充分的理由给他答案,他应该自己弄清楚。我从我的大学工作中获得了最优化的 isPrime 实现,但我认为仅仅发布答案并没有任何好处,所以他可以复制它并认为他非常擅长 Haskell。 3)整理你的生活,有一些尊严。
    【解决方案4】:

    1) 问题是sqrt 的类型为(Floating a) =&gt; a -&gt; a,但您尝试使用整数作为参数。因此,您必须先将 Integer 转换为 Floating,例如通过写sqrt (fromIntegral x)

    2) 我看不出为什么 == 不应该是惰性的,但是为了测试空集合,您可以使用 null 函数(这绝对是惰性的,因为它适用于无限列表):

    isPrime :: Integer->Bool
    isPrime x = null [y | y<-[2..floor (sqrt (fromIntegral x))], x `mod` y == 0]
    

    但为了获得更惯用的解决方案,请将问题分解为更小的子问题。首先,我们需要一个所有元素 y 的列表,其中 y*y

    takeWhile (\y ->  y*y <= x) [2..]
    

    那么我们只需要除 x 的元素:

    filter (\y ->  x `mod`y == 0) (takeWhile (\y ->  y*y <= x) [2..])
    

    然后我们需要检查该列表是否为空:

    isPrime x = null (filter (\y ->  x `mod`y == 0) (takeWhile (\y ->  y*y <= x) [2..]))
    

    如果这对你来说是 lispy,用 $ 替换一些括号

    isPrime x = null $ filter (\y ->  x `mod` y == 0) $ takeWhile (\y ->  y*y <= x) [2..]
    

    为了更加清楚,您可以“外包” lambda:

    isPrime x = null $ filter divisible $ takeWhile notTooBig [2..] where
         divisible y = x `mod`y == 0
         notTooBig y = y*y <= x
    

    您可以通过将 null $ 过滤器替换为 not $ any 来使其几乎“人类可读”:

    isPrime x = not $ any divisible $ takeWhile notTooBig [2..] where
         divisible y = x `mod`y == 0
         notTooBig y = y*y <= x
    

    【讨论】:

    • 最后一个声明适用于所有大于或等于 2 的数字。对于 1,它错误地表示它是素数,因为 1 不是素数。
    【解决方案5】:

    Landei 的解决方案很棒,但是,如果您想要更高效的实施¹,我们有(感谢 BMeph):

    -- list of all primes
    primes :: [Integer]
    primes = sieve (2 : 3 : possible [1..]) where
         sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
         possible (x:xs) = 6*x-1 : 6*x+1 : possible xs
    
    isPrime :: Integer -> Bool
    isPrime n = shortCircuit || (not $ any divisible $ takeWhile inRangeOf primes) where
        shortCircuit = elem n [2,3] || (n < 25 && ((n-1) `mod` 6 == 0 || (n+1) `mod` 6 == 0))
        divisible y = n `mod` y == 0
        inRangeOf y = y * y <= n
    

    “效率”来自于对常数素数的使用。它通过两种方式改进了搜索:

    1. Haskell 运行时可以缓存结果,因此不会评估后续调用
    2. 它通过逻辑消除了一系列数字 请注意,sieve 值只是一个递归表,其中的头表示 该列表是素数,并将其添加到其中。对于其余的列表,如果没有 其他值已经在组成数字的列表中,那么它也是素数 possible 是所有可能素数的列表,因为所有可能的素数都在 形式 6*k-1 或 6*k-1 除了 2 和 3 同样的规则也适用于 shortCircuit 以快速退出计算

    D.F. 的脚注
    ¹它仍然是一种非常低效的寻找素数的方法。如果您需要大于几千的素数,请不要使用试除法,而是使用筛子。 hackage 上有几个 far more efficient 实现。

    【讨论】:

    • 为了让 isPrime 工作 shortCircuit 必须被移除。例如,它匹配不是素数的 25。为了编译它,我也需要括号(不是 $ 任何可整除的 $ takeWhile inRangeOf 素数)。
    • primes 的代码在产生的素数上是 二次。 Eratosthenes 筛的理论时间复杂度为O(n*log n*log (log n)),在n 产生的素数中。试除法的理论复杂度为O(n^1.5/(log n)^0.5)。那为什么那个看起来很简单的试除法的代码执行得那么差呢?这是因为每个过滤器的启动必须推迟直到在输入流中看到素数的正方形。将输入流稀释到三分之一只会减少常数因子,仅此而已。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多