我需要一个简单的功能
is_square :: Int -> Bool
确定Int N是否为正方形(是否有整数x,使x * x = N)。
当然我可以写点像
is_square n = sq * sq == n
where sq = floor $ sqrt $ (fromIntegral n::Double)
但看起来很糟糕!也许有一种常见的简单方法来实现这样的谓词?
答案 0 :(得分:9)
这样想,如果你有一个正的int n
,那么你基本上是对1 ... n的数字范围进行二分搜索,找到第一个数字n'
其中n' * n' = n
。
我不知道Haskell,但这个F#应该很容易转换:
let is_perfect_square n =
let rec binary_search low high =
let mid = (high + low) / 2
let midSquare = mid * mid
if low > high then false
elif n = midSquare then true
else if n < midSquare then binary_search low (mid - 1)
else binary_search (mid + 1) high
binary_search 1 n
保证为O(log n)。易于修改完美的立方体和更高的功率。
答案 1 :(得分:9)
对于arithmoi
包中包含的Haskell中的大多数数论相关问题,有一个精彩的库。
使用Math.NumberTheory.Powers.Squares
库。
具体是isSquare'
功能。
is_square :: Int -> Bool
is_square = isSquare' . fromIntegral
图书馆经过优化,并且受到了人们更加专注于提高效率的人们的审查。虽然它目前还没有this kind of shenanigans在幕后进行,但随着图书馆的发展和未来的发展,得到更多优化。 View the source code了解其运作方式!
不要重新发明轮子,在可用时始终使用库。
答案 2 :(得分:6)
我认为您提供的代码是您获得的最快的代码:
is_square n = sq * sq == n
where sq = floor $ sqrt $ (fromIntegral n::Double)
此代码的复杂性为:一个sqrt,一个double乘法,一个cast(dbl-> int)和一个比较。您可以尝试使用其他计算方法将sqrt和乘法替换为整数算术和移位,但可能不会比一个sqrt和一个乘法更快。
唯一可能值得使用其他方法的地方是运行的CPU不支持浮点运算。在这种情况下,编译器可能必须在软件中生成sqrt和double乘法,并且您可以优化您的特定应用程序。
正如其他答案所指出的那样,大整数仍然存在局限性,但除非你要考虑这些数字,否则利用浮点硬件支持可能比编写自己的算法更好。 / p>
答案 3 :(得分:1)
维基百科的article on Integer Square Roots可以根据您的需求调整算法。牛顿的方法很好,因为它以二次方式收敛,即每步得到两倍的正确数字。
如果输入可能大于Double
,我建议您远离2^53
,之后并非所有整数都可以准确地表示为Double
。
答案 4 :(得分:1)
哦,今天我需要确定一个数字是否是完美的立方体,类似的解决方案非常慢。
所以,我提出了一个非常聪明的替代方案
cubes = map (\x -> x*x*x) [1..]
is_cube n = n == (head $ dropWhile (<n) cubes)
很简单。我想,我需要使用树来加快查找速度,但现在我将尝试这个解决方案,也许它对我的任务来说足够快。如果没有,我将用适当的数据结构编辑答案
答案 5 :(得分:1)
有时你不应该把问题分成太小的部分(比如支票is_square
):
intersectSorted [] _ = []
intersectSorted _ [] = []
intersectSorted xs (y:ys) | head xs > y = intersectSorted xs ys
intersectSorted (x:xs) ys | head ys > x = intersectSorted xs ys
intersectSorted (x:xs) (y:ys) | x == y = x : intersectSorted xs ys
squares = [x*x | x <- [ 1..]]
weird = [2*x+1 | x <- [ 1..]]
perfectSquareWeird = intersectSorted squares weird
答案 6 :(得分:1)
有一种非常简单的方法来测试一个完美的正方形 - 确切地说,你检查数字的平方根是否在其小数部分中具有除零之外的任何值。
我假设一个返回浮点的平方根函数,在这种情况下你可以做(Psuedocode):
func IsSquare(N) sq = sqrt(N) return (sq modulus 1.0) equals 0.0
答案 7 :(得分:1)
在对此问题的另一个答案的评论中,您讨论了memoization。请记住,当您的探针图案显示出良好的密度时,此技术会有所帮助。在这种情况下,这意味着一遍又一遍地测试相同的整数。您的代码有多大可能重复相同的工作,从而从缓存答案中获益?
您没有告诉我们您的输入分布,因此请考虑使用优秀criterion软件包的快速基准:
module Main
where
import Criterion.Main
import Random
is_square n = sq * sq == n
where sq = floor $ sqrt $ (fromIntegral n::Double)
is_square_mem =
let check n = sq * sq == n
where sq = floor $ sqrt $ (fromIntegral n :: Double)
in (map check [0..] !!)
main = do
g <- newStdGen
let rs = take 10000 $ randomRs (0,1000::Int) g
direct = map is_square
memo = map is_square_mem
defaultMain [ bench "direct" $ whnf direct rs
, bench "memo" $ whnf memo rs
]
此工作负载可能或可能不是您正在做的事情的公平代表,但正如所写,缓存未命中率似乎太高:
答案 8 :(得分:0)
它并不是特别漂亮或快速,但这里是一个基于牛顿方法的无投射,无FPA版本,可以(缓慢地)适用于任意大整数:
import Control.Applicative ((<*>))
import Control.Monad (join)
import Data.Ratio ((%))
isSquare = (==) =<< (^2) . floor . (join g <*> join f) . (%1)
where
f n x = (x + n / x) / 2
g n x y | abs (x - y) > 1 = g n y $ f n y
| otherwise = y
可能会加速一些额外的数论欺骗。