Haskell Eratosthenes 筛法和复合物列表

Haskell Sieve of Eratosthenes with list of composites

我要为一个项目实现Haskell中埃拉托色尼筛法的经典问题。我不必计算每个素数,而只需比较列表之间的数字。例如,我传递了一个潜在素数列表(参数 1)和一个复合素数列表(列表 2)。 sieve [2..10] [] 结果与列表 [2,3,5,7].

我认为我非常接近并且它编译了,但是它将每个项目附加到主要列表而不是丢弃组合。我的想法是,它将采用所有数字 2..10 或其他任何数字的列表 x 和复合材料的列表 y 使用 elem 来查看列表 x 的头部是否在列表 y 中找到,如果是,则追加到列出 z 并打印。提前致谢!

目前我的代码 returns 第一个列表中的所有内容并且拒绝排序。 sieve [2..10] [] 结果为 [2,3,4,5,6,7,8,9,10]

sieve ::[Int]->[Int]->[Int]
z = []
sieve [] [] = []
sieve x [] = x
sieve [] y = y
sieve xs ys = if ((elem (head xs)) ys) then (sieve (tail xs) ys)
    else ((head xs):z)

你显示的程序没有多大意义,首先 sieve x [] 将始终被使用,此外你应该检查一个元素是否可以被另一个列表整除。最后你应该使调用递归,这是你不使用 head xs : z 做的事情,因为 z 被定义为空列表。

让我们从基本情况开始:如果左边的列表是空的,不管第二个列表的内容如何,​​一个returns空列表。因为什么都不筛选就什么也得不到:

sieve [] _ = []

接下来我们寻找归纳案例,其模式为:

sieve (x:xs) ds = ...

现在我们需要枚举已找到元素的列表。从找到的元素any可以整除x的那一刻起,我们就知道这个数不是(相对)素数。此条件形式化为:

(==) 0 . mod x :: Integral b => b -> Bool

或遍历 ds 的列表:

any ((==) 0 . mod x) ds

如果存在这样的元素,我们就直接跳过这个元素,调用sieve xs ds的归纳情况。

如果没有这样的元素,我们将它添加到 ds 的列表中并发出它。结果是:x : sieve xs (x:ds)。因此,归纳情况是:

sieve (x:xs) ds | any ((==) 0 . mod x) ds = sieve xs ds
                | otherwise = x : sieve xs (x:ds)

我们可以通过为 sieve xs:

创建一个特定的变量来缩短它
sieve (x:xs) ds | any ((==) 0 . mod x) ds = rec ds
                | otherwise = x : rec (x:ds)
                where rec = sieve xs

完整的函数是:

sieve [] _ = []
sieve (x:xs) ds | any ((==) 0 . mod x) ds = rec ds
                | otherwise = x : rec (x:ds)
                where rec = sieve xs

您可以通过两种方式提高性能:

  • ds的末尾添加x。这确实是一个更昂贵的操作。但是一段时间后,您不会经常添加数字。这很有趣,因为在那种情况下 ys 看起来像 [2,3,5,7,11] 而不是 [11,7,5,3,2]。现在,一个数被 2 整除的几率 (50%) 大于一个数被 11 整除的几率 (9.9%)。最好先尝试最有可能成功的测试。

  • 此外,您可以在除数达到要测试的数字的平方根后结束检查:如果一个数字不能被小于该数字的数字整除,则肯定不是能被大于平方根的数整除。

一种更有效的方法是:

sieve [] _ = []
sieve (x:xs) ds | any ((==) 0 . mod x) $ takeWhile (\y -> y*y <= x) ds = rec ds
                | otherwise = x : rec (ds++[x])
                where rec = sieve xs

你所说的sieve通常被称为minus,从第一个列表中减去第二个列表,假设两者都是有序的,增加数字列表.那么只比较两个 head 元素就足够了,不需要任何 elem 调用。

但如果您为 z 提供了正确的定义,它仍然可以工作。 z=[] 只是一个占位符,让它编译(对吧?);这不是正确的定义。应该是:

sieve :: [Int] -> [Int] -> [Int]
-- z = []
sieve [] [] = []
sieve x [] = x
sieve [] y = y
sieve xs ys = if ((elem (head xs)) ys) then (sieve (tail xs) z)
    else ((head xs) : sieve (tail xs) ys )
      where
         z = ... -- need to remove (head xs) from ys

对于最后一条评论的任务,您可以使用例如delete 函数。

如果没有复合列表,这仍然不会为您生成素数列表,因此初始调用可以不会第二个列表为空(否则,您由于 sieve x [] = x 等式,d 得到与您相同的第一个参数:

primesAmong input = sieve input composites

但是 composites 是什么? Eratosthenes 的回答是,为什么,它们是质数的倍数!(试验部门说:复合物有其他质数作为它们的约数)。

给定一个质数,比如 2,我们只数:2,4,6,...;对于 3,比如说,它是 3,6,9,12,...;求它的倍数。让我们记下来:

composites = mutliplesOf primes
mutliplesOf primes = [ mult | p <- primes, mult <- [...] ]

这不太合适:这个 multiplesOf 需要一个参数:

primes = primesAmong input
primesAmong input = sieve input (mutliplesOf primes)

我们好像在追自己的尾巴;我们还没有 primes;我们可以用什么代替?求 non-primes 的倍数和质数有什么坏处吗?

当你有了运行代码后,还是想办法使用primes