在 Haskell 中编写一个 isPrime 函数
Writing an isPrime function in Haskell
isPrime :: Int -> Bool
isPrime n = leastDivisor n == n
leastDivisor :: Int -> Int
leastDivisor n = leastDivisorFrom 2 n
leastDivisorFrom :: Int -> Int -> Int
leastDivisorFrom k n | n `mod` k == 0 = k
| otherwise = leastDivisorFrom (k + 1) n
我的问题是:
- 这个功能有什么设计问题?
操作性太强了。可以等价地表示为(*)为
isPrime :: Integer -> Bool
isPrime n = [n] == take 1 [i | i <- [2..n], mod n i == 0]
所以它在视觉上更明显并且立即清晰,one-liner更容易处理。
尝试
GHCi> zipWith (-) =<< tail $ filter isPrime [2..]
[1,2,2,4,2,4,2,4,6,2,6,4,2,4,6,6,2,6,4,2,6,4,6,8,4,2,4,2,4,14,4,6,2,10,2,6,6,4,6,6,2,10,2,4,2,12,12,
4,2,4,6,2,10,6,6,6,2,6,4,2,10,14,4,2,4,14,6,10,2,4,6,8,6,6,4,6,8,4,8,10,2,10,2,6,4,6,8,4,2,4,12,8,4,
8,4,6,12,2,18,6,10,6,6,2,6,10,6,6,2,6,6,4,2,12,10,2,4,6,6,2,12,4,6,8,10,8,10,8,6,6,4,8,6,4,8,4,14,10
......
显示它有多慢。我们可以尝试将其重写为
isPrime n = null [i | i <- [2..n-1], mod n i == 0]
= none (\ i -> mod n i==0) [2..n-1]
= all (\ i -> mod n i > 0) [2..n-1]
= and [mod n i > 0 | i <- [2..n-1]]
但 [2..n-1]
并不 那个 比 [2..n]
短得多,不是吗?它应该短得多多,比那早多结束;甚至更短,里面有很多洞...
isPrime n = and [mod n p > 0 | p <- takeWhile (\p -> p^2 <= n) primes]
primes = 2 : filter isPrime [3..]
之后的下一个改进是 getting rid of mod
。
(*) 这表达了与您的 leastDivisor n == n
所做的完全相同的计算操作。 take 1
只取数字的第一个除数作为列表;它的长度必然是1;将它与单元素列表 [n]
进行比较等同于将第一个(即最小的)除数与数字 n
进行比较。正是您的代码在做什么。
但在这种形式下,它(可以说)是更清晰的代码,更直观。至少对我来说是这样。 :)
isPrime :: Int -> Bool
isPrime n = leastDivisor n == n
leastDivisor :: Int -> Int
leastDivisor n = leastDivisorFrom 2 n
leastDivisorFrom :: Int -> Int -> Int
leastDivisorFrom k n | n `mod` k == 0 = k
| otherwise = leastDivisorFrom (k + 1) n
我的问题是:
- 这个功能有什么设计问题?
操作性太强了。可以等价地表示为(*)为
isPrime :: Integer -> Bool
isPrime n = [n] == take 1 [i | i <- [2..n], mod n i == 0]
所以它在视觉上更明显并且立即清晰,one-liner更容易处理。
尝试
GHCi> zipWith (-) =<< tail $ filter isPrime [2..]
[1,2,2,4,2,4,2,4,6,2,6,4,2,4,6,6,2,6,4,2,6,4,6,8,4,2,4,2,4,14,4,6,2,10,2,6,6,4,6,6,2,10,2,4,2,12,12,
4,2,4,6,2,10,6,6,6,2,6,4,2,10,14,4,2,4,14,6,10,2,4,6,8,6,6,4,6,8,4,8,10,2,10,2,6,4,6,8,4,2,4,12,8,4,
8,4,6,12,2,18,6,10,6,6,2,6,10,6,6,2,6,6,4,2,12,10,2,4,6,6,2,12,4,6,8,10,8,10,8,6,6,4,8,6,4,8,4,14,10
......
显示它有多慢。我们可以尝试将其重写为
isPrime n = null [i | i <- [2..n-1], mod n i == 0]
= none (\ i -> mod n i==0) [2..n-1]
= all (\ i -> mod n i > 0) [2..n-1]
= and [mod n i > 0 | i <- [2..n-1]]
但 [2..n-1]
并不 那个 比 [2..n]
短得多,不是吗?它应该短得多多,比那早多结束;甚至更短,里面有很多洞...
isPrime n = and [mod n p > 0 | p <- takeWhile (\p -> p^2 <= n) primes]
primes = 2 : filter isPrime [3..]
之后的下一个改进是 getting rid of mod
。
(*) 这表达了与您的 leastDivisor n == n
所做的完全相同的计算操作。 take 1
只取数字的第一个除数作为列表;它的长度必然是1;将它与单元素列表 [n]
进行比较等同于将第一个(即最小的)除数与数字 n
进行比较。正是您的代码在做什么。
但在这种形式下,它(可以说)是更清晰的代码,更直观。至少对我来说是这样。 :)