在Haskell中测试单例列表内容的最有效或惯用方法?

时间:2014-08-21 03:57:50

标签: performance list haskell comparison

这个问题现在对我来说是严格的学术问题,但有一天我可以看到它有实际应用。通过Haskell自我教育,我成功地建立了无限的素数列表。其中一部分就是这个功能:

isPrime n
    | n < 2                         = False
    | head (primeFactorsOf n) == n  = True
    | otherwise                     = False

primeFactorsOf以升序返回数字素数列表。 1不是素数,因此素数n的素数因子是单身人士列表[n]。因此,第二个保护案可以替换为:

    | primeFactorsOf n == [n]       = True

其中一个比另一个更有效吗?如果没有,是一个更好的风格?我的预感是,调用头并比较两个简单数字比调用cons和比较两个单例列表更快,所以我已经得到的最好。但如果没有区别,我认为替代方案看起来更清晰。

2 个答案:

答案 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

这是目前效率最高的代码。它可以进一步重写(同样,remmod更快

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