Haskell这个素数筛码可以进一步简化吗?

Can this prime sieve code be further simplified in Haskell?

代码运行良好

primes = next [2 ..]
  where
    next (p : ps) = p : next ts
      where
        ts = filter (\x -> mod x p /= 0) ps

只是 GHCI 认为 next 中有一个不完整的模式。

嗯,从语法的角度来看,这是正确的。

但是显然'next'的输入不能为空

那么有没有除了添加声明之外的解决方案 ({-# OPTIONS_GHC -Wno-incomplete-patterns #-})?

详尽性检查器知道 next 的类型为 Num a => [a] -> [a]。空列表是 next 的有效参数,即使您实际上从未在空列表上 调用 next

这里的关键是您并不真的想要 Num a => [a] 作为您的参数类型。您知道它只会在无限列表上调用,因此请使用 没有 有限列表作为值的类型。

data Stream a = Cons a (Stream a)

sequence :: Num a => a -> Stream a
sequence x = Cons x (sequence (x + 1))

filterStream :: (a -> Bool) -> Stream a -> Stream a
filterStream p (Cons x xs) | p x = Cons x (filterStream p xs)
                           | otherwise = filterStream p xs

-- Since you'll probably want a list of values, not just a stream of them, at some point.
toList :: Stream a -> [a]
toList (Cons x xs) = x : toList xs

primes :: Stream Integer
primes = next (sequence 2)
  where 
    next (Cons x xs) = Cons x xs'
      where xs' = filterStream (\x -> mod x p /= 0) xs

Stream library provides a module Data.Stream that defines the Stream 类型和列表函数的众多类似物。

import qualified Data.Stream as S

-- S.toList exists as well.

primes :: Stream Integer
primes = next (S.fromList [2..])
  where next (Cons x xs) = Cons x (S.filter (\x -> mod x p /= 0) xs)

您的问题已经有了正确的答案。为了完整起见,另一个选项只是添加不需要的子句,即 we know will never be called:

primes = next [2 ..]
  where
  next (p : xs) =
       p : next [x | x <- xs, mod x p > 0]
  next _ = undefined

另一个更“old-style”的解决方案是通过显式调用 headtail 来分析参数(非常 而不是 推荐,一般情况下):

primes = next [2 ..]
  where
  next xs = let { p = head xs } in
       p : next [x | x <- tail xs, mod x p > 0]

这或许可以算作一种简化。

在一个不相关的注释中,您写道它“运行良好”。不幸的是,虽然确实产生了正确的结果,但速度非常慢。因为每次总是只从输入列表中取出 一个 元素,所以它的时间复杂度是 quadratic n 产生的素数。换句话说,primes !! n 需要 n 的二次方时间。 Empirically,

> primes !! 1000
7927     -- (0.27 secs, 102987392 bytes)
> primes !! 2000
17393    -- (1.00 secs, 413106616 bytes)
> primes !! 3000
27457    -- (2.23 secs, 952005872 bytes)

> logBase (2/1) (1.00 / 0.27)
1.8889686876112561                -- n^1.9
> logBase (3/2) (2.23 / 1.00)
1.9779792870810489                -- n^2.0

事实上,可以一次从输入中获取整串元素,直到当前素数的 平方 ,因此代码只需要大约 ~ n1.5 次,给或取一个 log 因子:

{-# LANGUAGE ViewPatterns #-}

primes_ = 2 : next primes_ [3 ..] 
  where 
  next (p : ps) (span (< p*p) -> (h, xs)) = 
      h ++ next ps [x | x <- xs, mod x p > 0]
  next _ _ = undefined

根据经验,我们再次得到

> primes !! 3000
27457     -- (0.08 secs,   29980864 bytes)
> primes !! 30000
350381    -- (1.81 secs,  668764336 bytes)
> primes !! 60000
746777    -- (4.74 secs, 1785785848 bytes)
> primes !! 100000
1299721   -- (9.87 secs, 3633306112 bytes)

> logBase (6/3)  (4.74 / 1.81)
1.388897361815054                 -- n^1.4
> logBase (10/6) (9.87 / 4.74)
1.4358377567888103                -- n^1.45

正如我们在这里看到的,复杂性优势也表现为绝对值的巨大加速。

那么这个筛相当于最优试分,不像题中的那个。当然刚提出的时候in 1976,Haskell还没有view patterns,实际上还没有Haskell本身。