如何判断一个数字是否是带递归的平方数?

时间:2018-06-06 03:44:02

标签: haskell recursion

我解决了以下练习,但我不是解决方案的粉丝:

  

使用递归编写函数isPerfectSquare,判断是否有   Int是一个完美的广场   isPerfectSquare 1 - >应该返回True   isPerfectSquare 3 - >应该返回False

<platform name="ios"> <resource-file src="path/to/your.xib" /> </platform> 部分适用于num+1的情况,其中一个部分我不喜欢一点,这是我的解决方案:

isPerfectSquare 0 and isPerfectSquare 1

这个问题有什么更优雅的解决方案?当然,我们不能使用sqrt,也不能使用浮点运算。

4 个答案:

答案 0 :(得分:5)

@luqui你的意思是这样吗?

pow n = n*n
perfectSquare pRoot pSquare | pow(pRoot) == pSquare = True
                            | pow(pRoot)>pSquare = perfectSquare (pRoot-1) pSquare
                            | otherwise = False
--
isPerfectSquare number = perfectSquare number number

我无法相信我没有看到它xD非常感谢!我一定很累了

答案 1 :(得分:3)

您可以执行某种&#34;二分搜索&#34;在一些隐含的正方形列表上。然而,当然存在一个问题,那就是我们首先需要一个上限。我们可以使用数字本身作为上限,因为对于所有整数正方形,正方形大于我们平方的值。

所以它看起来像:

isPerfectSquare n = search 0 n
    where search i k | i > k = False
                     | j2 > n = search i (j-1)
                     | j2 < n = search (j+1) k
                     | otherwise = True
              where j = div (i+k) 2
                    j2 = j * j

为了验证数字 n 是一个完美的正方形,我们因此有一个在 O(log n)中运行的算法,以防整数运算在常量中完成时间(例如,如果位数是固定的)。

答案 2 :(得分:1)

Wikipedia suggests using Newton's method.这就是看起来的样子。我们将从一些样板开始。 ensure是我经常使用的一个小组合子。它写得非常通用,但我已经加入了一个简短的评论,对于我们计划如何使用它应该是非常明确的。

import Control.Applicative
import Control.Monad

ensure :: Alternative f => (a -> Bool) -> a -> f a
ensure p x = x <$ guard (p x)
-- ensure p x | p x = Just x
--            | otherwise = Nothing

这是维基百科给出的在牛顿方法中迈出一步的公式的实现。 x是我们目前关于平方根的猜测,n是我们取平方根的数字。

stepApprox :: Integer -> Integer -> Integer
stepApprox x n = (x + n `div` x) `div` 2

现在我们可以递归调用这个步进函数,直到得到平方根的底部。由于我们使用整数除法,因此正确的终止条件是观察近似的下一步与当前步骤相等或更大。这是唯一的递归函数。

iterateStepApprox :: Integer -> Integer -> Integer
iterateStepApprox x n = case x' - x of
    0 -> x
    1 -> x
    _ -> iterateStepApprox x' n
    where x' = stepApprox x n

要将整个开发包装在一个漂亮的API中,要检查数字是否为正方形,我们可以检查其平方根的平面是否正方形。我们还需要选择一个起始近似值,但我们不必非常聪明 - 牛顿方法很快收敛于平方根。我们将选择一半的数字(四舍五入)作为近似值。为了避免被零和其他废话分开,我们将使零和负数特殊情况。

isqrt :: Integer -> Maybe Integer
isqrt n | n < 0 = Nothing
isqrt 0 = Just 0
isqrt n = ensure (\x -> x*x == n) (iterateStepApprox ((n+1)`div`2) n)

现在我们完成了!即使是大数字也很快:

> :set +s
> isqrt (10^10000) == Just (10^5000)
True
(0.58 secs, 182,610,408 bytes)

你的花费时间比宇宙离开计算的时间要长。它在我的测试中也比二进制搜索算法快一点。 (当然,not hand-rolling it yourself仍然快几个数量级,可能部分是因为它使用better, but more complicated, algorithm based on Karatsuba multiplication。)

答案 3 :(得分:0)

如果函数是递归的,那么它是原始递归的,因为它是所有递归函数的90%。因为这些fold是快速有效的。考虑到程序员的时间,保持简单和正确是很重要的。

现在,也就是说,cinsider sqrt等函数的文本模式可能会很有成效。 sqrt返回一个浮点数。如果一个数字是一个完美的正方形,则两个字符是&#34; .0&#34;在末尾。但是,在任何尾数的开头都可能出现这种模式。如果一个字符串进入,反过来,那么&#34; 0。&#34;在列表的顶部。

此函数接受一个数字并返回一个Bool

fps n = (take 2.reverse.show $ (n / (sqrt n))) == "0."

fps 10000.00001

fps 10000