如何在Haskell中编写递归素数检查器?

时间:2013-02-04 18:25:11

标签: haskell primes

我对Haskell(以及一般的函数式编程)非常陌生,我正在尝试一些基本练习来尝试理解语言。我正在编写一个“天真”的素数检查器,它将每个数字除以输入以检查是否有任何余数。到目前为止我学到的唯一构造是理解列表和递归函数,所以我受此限制。这是我正在尝试的:

isprime 1 = False
isprime 2 = True
isprime n = isprimerec n (n-1)

isprimerec _ 1 = False
isprimerec n t = if (n `rem` t) == 0 then False else isprimerec n (n-1)

目的是用户使用isprime n。然后isprime将使用isprimerec来确定该数字是否为素数。这是一个非常圆润的方式,但我对Haskell的知识有限,我不知道其他任何方式。

以下是我尝试这种情况时会发生的事情:

isprimerec 10 9

永远奔跑。我必须使用Ctrl + C来阻止它。

isprimerec 10 5

返回False。永远不会评估else部分,因此函数永远不会自行调用。

我不确定为什么会这样。此外,这是否接近Haskell程序员如何处理这个问题? (我并不是说检查素性,我知道这不是这样做的方法。我只是这样做,作为练习)。

4 个答案:

答案 0 :(得分:6)

问题出在这一行

isprimerec n t = if (n `rem` t) == 0 then False else isprimerec n (n-1)

您使用(n - 1)作为第二个参数,它应该是(t - 1)。还有一点,我想你想要isprimerec _ 1案例= True

关于这个是否是惯用的更普遍的问题,我认为你走在正确的轨道上。 ghci有一个不错的命令行调试器。我通过将您的代码放在一个文件中,加载它,然后发出命令:break isprimerec来找到它。然后,我调用了您的函数并使用:step逐步完成了它。

答案 1 :(得分:6)

你的错误是一个简单的错字;在isprimerec的末尾,您的第二个参数变为n-1而不是t-1。但除此之外,这个功能并不十分惯用。这是我如何写它的第一步:

isPrime :: (Ord a, Integral a) => a -> Bool
isPrime n | abs n <= 1 = False
isPrime 2 = True
isPrime n = go $ abs n - 1
  where go 1 = False
        go t = (n `rem` t /= 0) && go (t-1)

(我可能会将go称为checkDivisors,但go对于循环来说是惯用的。)请注意,这会暴露您的代码中的错误:一旦go为在isPrime本地,您不需要传递n,因此更准确地说,递归它是不正确的。我所做的改变是粗略的重要性:

  1. 我将isprimerec作为本地函数。没有其他人需要调用它,我们失去了额外的参数。

  2. 我完成了这个功能。 0没有理由失败,也没有任何理由因为负数而失败。 (从技术上讲,当且仅当 - p 是素数时, p 才是素数。)

  3. 我添加了一个类型签名。进入是一个很好的习惯。使用Integer -> Bool甚至Int -> Bool也是合理的。

  4. 我切换到interCaps而不是alllowercase。只是格式化,但这是习惯。

  5. 除了我可能会让事情更糟糕。在Haskell中通常不需要手动递归,如果我们完全摆脱它,你的bug就变得不可能了。您的函数会检查2n-1的所有数字是否都不会n,所以我们可以直接表达:

    isPrime :: (Ord a, Integral a) => a -> Bool
    isPrime n | abs n <= 1 = False
              | otherwise  = all ((/= 0) . (n `rem`)) [2 .. abs n - 1]
    

    您可以将其写在一行

    isPrime :: (Ord a, Integral a) => a -> Bool
    isPrime n = abs n > 1 && all ((/= 0) . (n `rem`)) [2 .. abs n - 1]
    

    但是看到最后两个实现中的任何一个我都不会感到惊讶。正如我所说,关于这些实现的好处是你的拼写错误无法用这些表示法形成:t隐藏在all的定义中,所以你不能不小心给它错误的价值。

答案 2 :(得分:3)

您的else分支已损坏,因为它每次都会调用isprimerec n (n-1)。您可能应该写isprimerec n (t-1)而不是让它倒计时。

您还可以使用更高阶函数all来简化这一过程。

isprime 1 = False
isprime n = all (\t -> n `rem` t /= 0) [2..(n-1)]

答案 3 :(得分:1)

好吧,你有两个错误:你的

isprimerec _ 1 = False
isprimerec n t = if (n `rem` t) == 0 then False else isprimerec n (n-1)

应该是

isprimerec _ 1 = True
isprimerec n t = if (n `rem` t) == 0 then False else isprimerec n (t-1)

或者,使用列表理解,

isprime n = n>1 && and [ rem n t /= 0 | t <- [n-1,n-2..2] ]

内部化那个额外的参数 t ,无论如何这都是技术性的! - A-ha,但那是什么and,你问?它就像这个递归函数foldr (&&) True :: [Bool] -> Bool。)

但是现在一个主要的算法缺点变得在视觉上明显:我们在错误的顺序中进行测试。如果我们按升序进行测试会更快:

isprime n = n>1 && and [ rem n t /= 0 | t <- [2..n-1] ]
如果我们停在sqrt

甚至更快

isprime n = n>1 && and [ rem n t /= 0 | t <- [2..q] ]
   where  q = floor (sqrt (fromIntegral n))
2 之后

或仅通过赔率进行测试(为什么通过 6 进行测试,如果我们通过 2进行测试< / em>已经?):

isprime n = n>1 && and [ rem n t /= 0 | t <- 2:[3,5..q] ]
   where  q = floor (sqrt (fromIntegral n))

或仅仅通过 primes (为什么要通过 9 进行测试,如果我们已经通过 3 进行了测试?):

isprime n = n>1 && and [ rem n t /= 0 | t <- takeWhile ((<= n).(^2)) primes ]
primes = 2 : filter isprime [3..]  

为什么在过滤素数时测试 evens - 是不是最好不首先生成它们?

primes = 2 : filter isprime [3,5..]  

但是isprime总是用 2 测试除法 - 但我们只用奇数输入它;所以,

primes = 2 : 3 : filter (noDivs (drop 1 primes)) [5,7..]
noDivs factors n = and [ rem n t /= 0 | t <- takeWhile ((<= n).(^2)) factors ]

为什么要生成 3 (即[9,15 ..] == map (3*) [3,5..])的倍数,以便稍后测试并删除它们? -

{-       [5,7..]
    ==   [j+i | i<-[0,2..], j<-[5]]                 -- loop unrolling, 3x:
    ==   [j+i | i<-[0,6..], j<-[5,7,9]]
    == 5:[j+i | i<-[0,6..], j<-[7,9,11]]            
    == 5:[7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43, ...
    \\   [  9,      15,      21,      27,      33,      39,       ...
    ==   [j+i | i<-[0,6..], j<-[  9   ]]
-}
primes = 2:3:5: filter (noDivs (drop 2 primes)) 
         [j+i | i<-[0,6..], j<-[7,  11]]

我们可以提前跳过 5 的倍数以及 (作为Euler's sieve中的另一个步骤,euler (x:xs) = x : euler (xs `minus` map (x*) (x:xs))):

--       [j+i | i<-[0, 6..], j<-[7, 11]]            -- loop unrolling, 5x:
--  == 7:[j+i | i<-[0,30..], j<-[11,13,17,19,23,25,29,31,35,37]]
--  \\   [j+i | i<-[0,30..], j<-[               25,      35   ]]

primes = 2:3:5:7: filter (noDivs (drop 3 primes)) 
         [j+i | i<-[0,30..], j<-[11,13,17,19,23,   29,31,   37]]

...但已经开始了far enough, for now