整数n的除数列表(Haskell)

时间:2012-09-06 01:38:44

标签: performance haskell numbers primes

我目前有以下函数来获取整数的除数:

-- All divisors of a number
divisors :: Integer -> [Integer]
divisors 1 = [1]
divisors n = firstHalf ++ secondHalf
    where firstHalf = filter (divides n) (candidates n)
          secondHalf = filter (\d -> n `div` d /= d) (map (n `div`) (reverse firstHalf))
          candidates n = takeWhile (\d -> d * d <= n) [1..n] 

我最后将filter添加到secondHalf,因为当n是素数的平方时,除数重复。这似乎是解决这个问题的一种非常低效的方法。

所以我有两个问题:如何衡量我的算法中是否真的是瓶颈?如果是的话,当n是素数的平方时,如何找到更好的方法来避免重复?

2 个答案:

答案 0 :(得分:2)

要确定瓶颈所在位置,请将三个辅助定义(firstHalf,secondHalf,候选)放在顶层,然后使用分析器运行代码:ghc -prof --make divisors.hs ./divisors 100 +RTS -p -RTS

另外,你知道最大的候选人是sqrt n,所以不要做那么多乘法d*d,而只考虑[1..floor (sqrt n)]

为了更好的算法,你应该拿一本数学书,因为它不是与haskell相关的问题......你可以考虑的事情:如果“a除b”,那么对于a的所有除数d,d也将b除。 如果给定的d除以b,你会想要使用memoization或动态编程来避免多次检查(例如,如果15和27除以b,那么你需要在数学上只检查一次3除以b。其他时候,你只要看看3是否在b)的除数表中。

答案 1 :(得分:1)

你不需要测试所有反向后半部分的元素。你知道如果存在平方根,它就是那里的头元素:

          secondHalf = let (r:ds) = [n `div` d | d <- reverse firstHalf]
                       in [r | n `div` r /= r] ++ ds

这假定n为正。

以不同方式处理数字的sqrt的一种更简单的方法是单独处理

divs n = 
  let 
    r = floor $ sqrt $ fromIntegral n 
    (a,b) = unzip $ (1,n) : [(d, q) | d<-[2..r-1], let (q,r)=quotRem n d, r==0]
  in
    if r*r==n
      then a ++ r : reverse b
      else a ++ reverse b

这样我们可以免费获得下半场,作为上半场的一部分。

但这很难成为您应用程序的瓶颈,因为算法本身效率低下。 generate the divisors from a number's prime factorization通常要快得多。通过试验划分的推理因子化可以更快,因为我们除去了每个除数,因为它被分解,从而减少了因子的数量,从而减少了被试算的除数(直到减去< / em>数字的平方根)。例如,在分解过程中尝试12348 = 2*2*3*3*7*7*7并且没有高于7的因子,而在divs 12348中,数字12348除以从2到{{1}的所有数字}}:

110