我目前有以下函数来获取整数的除数:
-- 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
是素数的平方时,如何找到更好的方法来避免重复?
答案 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