【问题标题】:What's the way to determine if an Int is a perfect square in Haskell?确定 Int 是否是 Haskell 中的完美正方形的方法是什么?
【发布时间】:2015-03-30 06:55:08
【问题描述】:

我需要一个简单的函数

is_square :: Int -> Bool

确定 Int N 是否为完美正方形(是否存在整数 x 使得 x*x = N)。

当然我可以写类似的东西

is_square n = sq * sq == n
    where sq = floor $ sqrt $ (fromIntegral n::Double)

但它看起来很糟糕!也许有一种常见的简单方法来实现这样的谓词?

【问题讨论】:

标签: algorithm haskell sqrt


【解决方案1】:

这样想,如果你有一个正整数 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)。易于修改完美立方体和更高的幂。

【讨论】:

  • 我非常喜欢这个解决方案。我一直对二进制搜索对不同事物的有用性感到惊讶。但是,O(log n) 具有误导性。您将执行 O(log n) 次迭代,但是在每次迭代中,您都有一个隐藏的 mid*mid。对一个数求平方大约需要 O(mlogm)。 m 接近 sqrt(n),所以假设 m = sqrt(n)。这最终的效率实际上是 O(log n) * O(m log m) for m = sqrt(n)。尽管如此,二分搜索+1:P
  • 问题指定 Int 在 Haskell 中是固定精度(通常是 32 位),所以我更喜欢问题中使用的浮点方法。如果NInteger(任意精度),我会使用你的方法。
【解决方案2】:

arithmoi 包中包含一个 wonderful 库,可以解决 Haskell 中大多数与数论相关的问题。

使用Math.NumberTheory.Powers.Squares 库。

特别是isSquare' 函数。

is_square :: Int -> Bool
is_square = isSquare' . fromIntegral

该库已经过优化,并且经过了比您或我更致力于效率的人的严格审查。虽然它目前没有 this kind of shenanigans 在后台运行,但随着库的发展和获得更多,它可能会在未来优化。 View the source code 了解它的工作原理!

不要重新发明轮子,总是在可用时使用库。

【讨论】:

  • 我正在编写我自己的数论库来取乐。我的意思是了解我在其中的功能是如何工作的。该库中函数的最坏情况是:
【解决方案3】:

我认为您提供的代码是您将获得的最快的代码:

is_square n = sq * sq == n
    where sq = floor $ sqrt $ (fromIntegral n::Double)

这段代码的复杂度是:1次sqrt,1次双乘,1次强制转换(dbl->int),1次比较。您可以尝试使用其他计算方法来仅用整数算术和移位来替换 sqrt 和乘法,但它可能不会比一个 sqrt 和一个乘法更快。

唯一值得使用另一种方法的地方是,如果您运行的 CPU 不支持浮点运算。在这种情况下,编译器可能必须在软件中生成 sqrt 和双倍乘法,您可以在针对特定应用程序进行优化时获得优势。

正如其他答案所指出的,大整数仍然存在限制,但除非您要遇到这些数字,否则利用浮点硬件支持可能比编写自己的算法更好。

【讨论】:

  • 我只是觉得有一个简单而漂亮的解决方案,不需要两次类型转换:) 好的,谢谢!
  • 分析我的应用程序显示 57% 的时间花费在 is_square 函数中:(
  • 您可能需要做一些缓存(不要两次计算相同的整数),或者最初预先计算所有整数。使用非 Haskell 语言: bool[] isSquare = new bool[100000]; for(int i = 1; i
  • 记住is_square,我从来没有想过!但我正在使用haskell,这里并不是那么简单。顺便说一句,我不明白为什么记住纯函数不是 haskell 的一部分
  • 自动记忆东西是一个巨大的空间泄漏。这就是为什么你必须有意识地自己做。
【解决方案4】:

在对此问题的另一个答案的评论中,您讨论了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
              ]

这个工作负载可能或可能不公平地代表您正在做的事情,但正如所写,缓存未命中率似乎太高了:

【讨论】:

    【解决方案5】:

    维基百科的article on Integer Square Roots 有算法可以适应你的需要。牛顿的方法很好,因为它是二次收敛的,也就是说,每一步你得到两倍的正确数字。

    如果输入可能大于2^53,我建议您远离Double,之后并非所有整数都可以精确表示为Double

    【讨论】:

    • 我不需要很复杂的算法,我只是觉得有一个简单漂亮的解决方案,不需要两次类型转换:)
    • @valya:编写isqrt 函数将消除类型转换。
    【解决方案6】:

    哦,今天我需要确定一个数字是否是完美的立方体,并且类似的解决方案非常慢。

    所以,我想出了一个非常聪明的替代方案

    cubes = map (\x -> x*x*x) [1..]
    is_cube n = n == (head $ dropWhile (<n) cubes)
    

    非常简单。我想,我需要使用树来更快地查找,但现在我会尝试这个解决方案,也许它对我的任务来说足够快。如果没有,我将使用适当的数据结构编辑答案

    【讨论】:

    • 不知道会不会比原来的快。它需要执行更多的指令。它可能会更快,具体取决于 Haskell 如何处理 head/dropWhile 以及您的数字有多大。
    • 使用不同的数据结构(SeqVector)和二分查找可能会快一个数量级
    【解决方案7】:

    有时你不应该把问题分成太小的部分(比如检查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
    

    【讨论】:

    • 哦,很有趣!我会考虑如何让这个更适合我
    【解决方案8】:

    有一种非常简单的方法可以测试完美平方 - 顾名思义,就是检查数字的平方根中的小数部分是否有任何非零值。
    我假设一个返回浮点的平方根函数,在这种情况下你可以做(​​Psuedocode):

    func IsSquare(N)  
       sq = sqrt(N)
       return (sq modulus 1.0) equals 0.0
    

    【讨论】:

    • isSquare b n = (mod' (logBase b n) 1.0) == 0.0 -- mod' from Data.Fixed
    【解决方案9】:

    它不是特别漂亮或快速,但这是一个基于牛顿方法的无强制转换、无 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
    

    可能会通过一些额外的数论技巧来加快速度。

    【讨论】:

    • 谢谢!但这与我的任务相反
    猜你喜欢
    • 1970-01-01
    • 2010-09-25
    • 1970-01-01
    • 2022-08-23
    • 2015-07-05
    • 1970-01-01
    • 2015-11-06
    • 2016-03-07
    相关资源
    最近更新 更多