8
\$\begingroup\$

I want to convert an integer to a perfect square by multiplying it by some number. That number is the product of all the prime factors of the number which not appear an even number of times. Example 12 = 2 x 2 x 3; 2 appears twice (even number of times) but 3 just once (odd number of times), so the number I need to multiply 12 by to get a perfect square is 3. And in fact 12 x 3 = 36 = 6 * 6.

I converted my code to Haskell and would like to know what suggestions you have.

import Data.List (group)

toPerfectSquare :: Int -> Int
toPerfectSquare n = product . map (\(x:_) -> x) . filter (not . even . length) . group $ primefactors n

primefactors :: Int -> [Int]
primefactors n = prmfctrs' n 2 [3,5..]
  where
    prmfctrs' m d ds | m < 2     = [1]
                     | m < d^2   = [m]
                     | r == 0    = d : prmfctrs' q d ds
                     | otherwise = prmfctrs' m (head ds) (tail ds)
      where (q, r) = quotRem m d

Sorry about the naming, I'm bad at giving names.

One particular doubt I have is in the use of $ in toPerfectSquare, that I first used . but it didn't work and I needed to use parenthesis. Why? And is it usual to have that many compositions in one line?

\$\endgroup\$
7
\$\begingroup\$

We can replace some custom functions or constructs by standard library ones:

  • \(x:_) -> x is called head
  • not . even is called odd

Next, 1 is not a prime, and 1 does not have a prime factorization. Since product [] yields 1, we can use [] instead in prmfctrs'.

The worker prmfctrs' is a mouthful. Workers are usually called the same as their context (but with an apostrophe, so primefactors') or short names like go.

And last but not least, we can use @ bindings to pattern match on the head, tail and the whole list at once.

If we apply those suggestions, we get

import Data.List (group)

toPerfectSquare :: Int -> Int
toPerfectSquare n = product . map head . filter (odd . length) . group $ primefactors n

primefactors :: Int -> [Int]
primefactors n = go n 2 [3,5..]
  where
    go m d ds@(p:ps) | m < 2     = []
                     | m < d^2   = [m]
                     | r == 0    = d : go q d ds
                     | otherwise = go m p ps
      where (q, r) = quotRem m d

In theory, we can even get rid of a parameter in go, namely the d, so that we always just look at the list of the divisors:

import Data.List (group)

toPerfectSquare :: Int -> Int
toPerfectSquare n = product . map head . filter (odd . length) . group $ primefactors n

primefactors :: Int -> [Int]
primefactors n = go n $ 2 : [3,5..]
  where
    go m dss@(d:ds) | m < 2     = []
                    | m < d^2   = [m]
                    | r == 0    = d : go q dss
                    | otherwise = go m ds
      where (q, r) = m `quotRem` d

We could also introduce another function \$f\$, so that for any \$a,b \in \mathbb N\$ we get a pair \$(n,y) \in \mathbb N^2\$ such that

$$ a^n y = b $$ If we had that function, we could write use it to check easily whether the power of a given factor is even or odd. However, that function and its use in toPerfectSquare are left as an exercise.

\$\endgroup\$
  • \$\begingroup\$ You mean that function f as an optimization to the algorithm or to clean a bit the code? My problem is that I cannot “think in Haskell”, so I'm converting code I already have in other language to learn more about how to program in Haskell. By the way, I think you removed map and you wanted to keep map head (almost everytime a function exists, but it's difficult to know it exists, so many functions to learn, but I will keep learning to use Hoogle). And nice to know product [] gives 1 (I didn't kno, which is precisely why I used [1] instead of []). \$\endgroup\$ – Manuel Mar 18 at 12:32
  • \$\begingroup\$ @Manuel Yeah, wanted to use map head, sorry. The function f is both a clean-up, as well as an optimization, as you can get rid of the list of primefactors. However, it's not necessary to solve this and is therefore just an optional exercise. \$\endgroup\$ – Zeta Mar 18 at 14:04
  • \$\begingroup\$ I thought a bit and I think it does solve the problem more concisely, but I couldn't figure how to do it in Haskell directly (I would need to write in other language and then try to translate), so I will leave it for now. Thanks again for the answer! By the way at first i used sqrt m < d but changed to m < d^2 because I thought it would be more efficient, is that the case? \$\endgroup\$ – Manuel Mar 18 at 21:06
  • \$\begingroup\$ Usually yes. sqrt is a very expensive operation in most programming languages, whereas multiplication is a single assembly instruction as long as we're using native CPU integers. However, Haskell being Haskell, sqrt doesn't even work on Int, as sqrt only works on floating point numbers. So m < d*d was not only more efficient, but more idiomatic Haskell in that regard :) \$\endgroup\$ – Zeta Mar 18 at 21:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.