这个问题现在对我来说是严格的学术问题,但有一天我可以看到它有实际应用。通过Haskell自我教育,我成功地建立了无限的素数列表。其中一部分就是这个功能:
isPrime n
| n < 2 = False
| head (primeFactorsOf n) == n = True
| otherwise = False
primeFactorsOf
以升序返回数字素数列表。 1
不是素数,因此素数n
的素数因子是单身人士列表[n]
。因此,第二个保护案可以替换为:
| primeFactorsOf n == [n] = True
其中一个比另一个更有效吗?如果没有,是一个更好的风格?我的预感是,调用头并比较两个简单数字比调用cons和比较两个单例列表更快,所以我已经得到的最好。但如果没有区别,我认为替代方案看起来更清晰。
答案 0 :(得分:5)
如果速度非常重要,唯一可以确定的方法是对其进行基准测试,我建议criterion。哪个提供更好的性能并不完全清楚。如果primeFactorsOf
被内联,则编译器可能会注意到您在第二种情况下比较两个列表并自动删除装箱。或者它可能不会,在这种情况下你的预感可能是正确的。
至于哪种风格更好,第二种形式是。像head
这样的部分函数在大多数情况下最好避免。但是,也许你可以做得更好?
isPrime n = case primeFactorsOf n of
[n'] | n == n' && n >= 2 -> True
_ -> False
或者您可以将n >= 2
的支票放在外面。或者,如果您知道primeFactorsOf
返回n < 2
的空列表,则可以完全省略它。
编辑:高尔夫球到
isPrime n = case primeFactorsOf n of
[_] -> n > 1
_ -> False
答案 1 :(得分:0)
(总重写)是的,您的第一个变体更好。它相当于
isPrime n = n > 1 && head fs == n
where
fs = primeFactorsOf n
第二个变体是
isPrime n = n > 1 && fs == [n]
where
fs = primeFactorsOf n
相当于
isPrime n = n > 1 && head fs == n && null (tail fs)
where
fs = primeFactorsOf n
对于复合材料,两者的行为都相同,但对于素数,后一种变体将不必要地执行额外的测试。
我们可能会试图“简化和改进”最后一个变种
isPrime n = n > 1 && null (tail fs)
where
fs = primeFactorsOf n
这将为素数提供数字比较操作,但对于复合材料,它实际上会强制计算第二个因子n
,这是不必要的,这可能是非常昂贵的(例如n = 2 * p
p
1}}一些大素数)。您的第一个变体会找到第一个因素并立即返回False
。
要更正此问题,我们可以使用isPrime
执行primeFactors
的手动“融合”。假设它被定义为
primeFactors n = factor n primes
where
factor n (p:ps)
| p*p > n = [n]
| n `mod` p == 0 = p : factor (n `div` p) (p:ps)
| otherwise = factor n ps
与上一版本融合,变为
isPrime n = n > 1 && hasOneFactor n primes -- (null . tail . primeFactorsOf)
where -- == hasOneFactor
hasOneFactor n (p:ps)
| p*p > n = True
| n `mod` p == 0 = False
| otherwise = hasOneFactor n ps
这是目前效率最高的代码。它可以进一步重写(同样,rem
比mod
更快
isPrime n = n > 1 && hasOneFactor primes -- no need to pass `n` around
where
hasOneFactor (p:ps) = p*p > n ||
( n `rem` p /= 0 &&
hasOneFactor ps )
或
isPrime n = n > 1 && foldr (\p r-> p*p > n || (rem n p /= 0 && r))
undefined primes