3

I solved the following exercise, but I'm not a fan of the solution:

Write the function isPerfectSquare using recursion, to tell if an Int is a perfectSquare isPerfectSquare 1 -> Should return True
isPerfectSquare 3 -> Should return False

the num+1 part is for the case for isPerfectSquare 0 and isPerfectSquare 1, one of the parts I don't like one bit, this is my solutiuon:

perfectSquare 0 1 = [0] ++ perfectSquare 1 3 perfectSquare current diff = [current] ++ perfectSquare (current + diff) (diff + 2) isPerfectSquare num = any (==num) (take (num+1) (perfectSquare 0 1)) 

What is a more elegant solution to this problem? of course we can't use sqrt, nor floating point operations.

3
  • 2
    Squaring is a monotone operation, so you can binary search for its inverse, which would be much faster than what you have here (and is pretty naturally recursive) Commented Jun 6, 2018 at 4:05
  • Also, to whet your appetite, the pattern that perfectSquare uses is actually known as corecursive (but for the purposes of a class it will count as recursive I'm sure) Commented Jun 6, 2018 at 4:08
  • @luqui thanks, I just wrote a comment below Commented Jun 6, 2018 at 4:20

4 Answers 4

5

@luqui you mean like this?

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 

I can't believe I didn't see it xD thanks a lot! I must be really tired

Sign up to request clarification or add additional context in comments.

3 Comments

No I was talking about this algorithm (but using a function in lieu of an array), but I'm happy you got something you're happy with.
The same linear search you implemented recursively above can also be performed by generating the infinite list of squares squares = [n*n | n <- [0..]], and dropping the squares which are too small dropWhile (< number) squares. This results in a list whose first element is number is and only if number is a square. (Binary search is more efficient, though, as luqui pointed out)
Your original solution is probably better than this one: it does a linear search up from 0 to the square root, whereas this one does a linear search down from the value to its square root. So your original is O(sqrt n) runtime, asymptotically better than this one's O(n) runtime.
3

You can perform some sort of "binary search" on some implicit list of squares. There is however a problem of course, and that is that we first need an upper bound. We can use as upper bound the number itself, since for all integral squares, the square is larger than the value we square.

So it could look like:

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 

To verify that a number n is a perfect square, we thus have an algorithm that runs in O(log n) in case the integer operations are done in constant time (for example if the number of bits is fixed).

Comments

1

Wikipedia suggests using Newton's method. Here's how that would look. We'll start with some boilerplate. ensure is a little combinator I've used fairly frequently. It's written to be very general, but I've included a short comment that should be pretty explanatory for how we'll plan to use it.

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 

Here's the implementation of the formula given by Wikipedia for taking one step in Newton's method. x is our current guess about the square root, and n is the number we're taking the square root of.

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

Now we can recursively call this stepping function until we get the floor of the square root. Since we're using integer division, the right termination condition is to watch for the next step of the approximation to be equal or one greater to the current step. This is the only recursive function.

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

To wrap the whole development up in a nice API, to check if a number is a square we can just check that the floor of its square root squares to it. We also need to pick a starting approximation, but we don't have to be super smart -- Newton's method converges very quickly for square roots. We'll pick half the number (rounded up) as our approximation. To avoid division by zero and other nonsense, we'll make zero and negative numbers special cases.

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) 

Now we're done! It's pretty fast even for large numbers:

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

Yours would spend rather a longer time than the universe has got left computing that. It is also marginally faster than the binary search algorithm in my tests. (Of course, not hand-rolling it yourself is several orders of magnitude faster still, probably in part because it uses a better, but more complicated, algorithm based on Karatsuba multiplication.)

Comments

0

If the function is recursive then it is primitive recursive as are 90% of all recursive functions. For these folds are fast and effective. Considering the programmers time, while keeping things simple and correct is important.

Now, that said, it might be fruitful to cinsider text patterns of functions like sqrt. sqrt return a floating point number. If a number is a perfect square then two characters are ".0" at the end. The pattern might occur, however, at the start of any mantissa. If a string goes in, in reverse, then "0." is at the top of the list.

This function takes a Number and returns a Bool

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

fps 10000.00001

False

fps 10000 

True

2 Comments

Why n / sqrt n instead of just sqrt n? And leaving aside all the rounding problems that go along with floating point, this falls over at 1e14 even though sqrt 1e14 is exactly 1e7, because 1e7 shows as 1.0e7, not 10000000.0. It would be better to use properFraction or mod' or similar to check whether the number is a whole number.
Indeed, the n / sqrt n was residual from my iterative process. I glossed over it in the final version but ignored it after I got what I wanted. Requirements are missing. The examples were 1 and 3, that is, not large. The question, was more how to do it recursively. It was not specified what. I could not get mod to fit. My weakness. My goals are more toward the simple.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.