我试图找到各种3D椭圆内的所有整数格点。
我希望我的程序取一个整数N,并计算形式ax ^ 2 +的椭圆内的所有格点由^ 2 + cz ^ 2 = n,其中a,b,c是固定整数和n介于1和N之间。此程序应返回格式的N个元组(n,numlatticePointsWithinEllipse n)。
我目前正在通过计算椭圆上的点ax ^ 2 +乘以^ 2 + cz ^ 2 = m,对于介于0和n之间的m,然后在m上求和。我最初也只是看x,y和z都是正面的,然后通过置换它们的符号来加入底片。
理想情况下,我希望在数小时内达到N = 1,000,000+的数量
以x ^ 2 + y ^ 2 + 3z ^ 2 = N为例,这里是我目前使用的Haskell代码:
import System.Environment
isqrt :: Int -> Int
isqrt 0 = 0
isqrt 1 = 1
isqrt n = head $ dropWhile (\x -> x*x > n) $ iterate (\x -> (x + n `div` x) `div` 2) (n `div` 2)
latticePointsWithoutNegatives :: Int -> [[Int]]
latticePointsWithoutNegatives 0 = [[0,0,0]]
latticePointsWithoutNegatives n = [[x,y,z] | x<-[0.. isqrt n], y<- [0.. isqrt (n - x^2)], z<-[max 0 (isqrt ((n-x^2 -y^2) `div` 3))], x^2 +y^2 + z^2 ==n]
latticePoints :: Int -> [[Int]]
latticePoints n = [ zipWith (*) [x1,x2,x3] y | [x1,x2,x3] <- (latticePointsWithoutNegatives n), y <- [[a,b,c] | a <- (if x1 == 0 then [0] else [-1,1]), b<-(if x2 == 0 then [0] else [-1,1]), c<-(if x3 == 0 then [0] else [-1,1])]]
latticePointsUpTo :: Int -> Int
latticePointsUpTo n = sum [length (latticePoints x) | x<-[0..n]]
listResults :: Int -> [(Int, Int)]
listResults n = [(x, latticePointsUpTo x) | x<- [1..n]]
main = do
args <- getArgs
let cleanArgs = read (head args)
print (listResults cleanArgs)
我用
编译了这个ghc -O2 latticePointsTest
但使用PowerShell&#34; Measure-Command&#34;命令,我得到以下结果:
Measure-Command{./latticePointsTest 10}
TotalMilliseconds : 12.0901
Measure-Command{./latticePointsTest 100}
TotalMilliseconds : 12.0901
Measure-Command{./latticePointsTest 1000}
TotalMilliseconds : 31120.4503
再向上几个数量级会使我们达到天数,而不是几小时或几分钟。
我使用的算法有什么根本原因吗?我的代码不能很好地扩展,有什么核心原因吗?任何指导将不胜感激。我可能还想在&#34; latticePoints&#34;之间处理数据。和&#34; latticePointsUpTo&#34;,所以我不能完全依赖聪明的数字理论计数技术 - 我需要保留基础元组。
答案 0 :(得分:4)
我会尝试一些事情:
isqrt
对您正在工作的值范围无效。只需使用浮点sqrt
函数:
isqrt = floor $ sqrt ((fromIntegral n) :: Double)
或者,不要计算整数平方根,而是在列表推导中使用这样的逻辑:
x <- takeWhile (\x -> x*x <= n) [0..],
y <- takeWhile (\y -> y*y <= n - x*x) [0..]
另外,我会使用x*x
之类的表达式而不是x^2
。
最后,为什么不用这样的方式计算解决方案的数量:
sols a b c n =
length [ () | x <- takeWhile (\x -> a*x*x <= n) [0..]
, y <- takeWhile (\y -> a*x*x+b*y*y <= n) [0..]
, z <- takeWhile (\z -> a*x*x+b*y*y+c*z*z <= n) [0..]
]
这并不能完全计算出您想要的相同答案,因为它没有考虑正面和负面解决方案,但您可以轻松修改它以计算您的答案。我们的想法是使用一个列表理解而不是迭代n
的各种值并求和。
最后,我认为使用floor
和sqrt
来计算积分平方根在这种情况下是完全安全的。此代码通过对{x * x)== x的所有x <= 3037000499:
sqrt
来验证整数平方根
testAll :: Int -> IO ()
testAll n =
print $ head [ (x,a) | x <- [n,n-1 .. 1], let a = floor $ sqrt (fromIntegral (x*x) :: Double), a /= x ]
main = testAll 3037000499
注意我在64位GHC上运行它 - 否则只使用Int64
而不是Int
,因为在任何一种情况下双打都是64位。只需一分钟左右来验证。
这表明如果y <= 3037000499 ^ 2,取得sqrt y的最低限度将永远不会得到错误答案。