The following code is a solution to a Hackerrank problem in Haskell. Given a list of polynomial coeficients
a and exponents
b, find the area under the polynomial’s curve from
r using a step size of
0.001. Then to find the volume of the curve rotated around the x-axis.
As a newbie to Haskell, I ran into rather frustrating type errors, and I feel like I’ve used hacky solutions to fix them. Specifically, I think there’s a lot on unnecessary code needed to make sure all of the
* + - ^ operators have the same type for left and right parameters.
Any tips on naming conventions would also be appreciated.
import Text.Printf (printf) -- This function should return a list [area, volume]. solve :: Int -> Int -> [Int] -> [Int] -> [Double] solve l r a b = [ sum [(poly x as bs) / 1000.0 | x <- xs], -- area under curve sum [pi * (poly x as bs) ^ 2 / 1000.0 | x <- xs] -- volume under rotated curve ] where as = map fromIntegral a bs = map fromIntegral b ls = fromIntegral l rs = fromIntegral r xs = [ls, ls+0.001..rs] poly :: Double -> [Double] -> [Double] -> Double poly x a b = sum [fst c * x ** snd c | c <- zip a b] --Input/Output. main :: IO () main = getContents >>= mapM_ (printf "%.1fn"). ([a, b, [l, r]] -> solve l r a b). map (map read. words). lines
The I/O is witchcraft to me, so I’m not too concerned with reviews of that portion.
solve‘s type is a disaster. I guess its provided by HackerRank, so that’s not a surprise and not your fault. This is also the reason why you get so many type errors. Your
poly reflects that perfectly: you want to use
But while we’re at
poly, let’s change it. First of all, the coefficients and exponents should stay together in my opinion, so let’s change
poly‘s type slightly:
poly :: [(Double, Double)] -> Double -> Double poly as x = sum [fst c * x ** snd c | c <- as]
x as second argument and not as first? Because you usually have a function
f that is a polynom
f = poly coeffs. That way you can easily give a name to
poly coeffs and then only use
f 2 and so on:
ghci> let f = poly [(1,2)] ghci> f 1 1 ghci> f 2 4 ghci> f 3 9
poly is still not optimal, we can get rid of
snd, and we can relax the types if we want to:
poly :: (Num a, Integral n) => [(a, n)] -> a -> a poly cs x = sum [a * (x ^ b) | (a,b) <- cs]
You can, of course, keep the type to
Double (you have to change
^ back to
solve. Let’s use proper types first:
solve :: Double -> Double -> [(Double,Int)] -> (Double, Double)
We lifted the
Int -> Double part out of the function. Also, instead of a list, we return a pair. We really want two and only two values as a result. Not arbitrary many, which is modelled with a list. The implementation doesn’t differ too much from yours, apart from the pair and some removed magic numbers:
solve l r cs = ( sum [ (p x) * step | x <- xs] , sum [pi * (p x) ^ 2 * step | x <- xs] ) where p = poly cs step = 0.001 xs = [l,l + step..r]
Remark: You really don’t want to use
0.001 without a proper name in your code. Later you might notice that you need
step = 0.0005, and suddenly you have to change all your numbers throughout the code.
IO can be simple
And now to the last part. Proper IO. Many challenge sites give you an IO skeleton that is rather unusable, to be honest. It’s often somewhat clever, but it really sucks for strict type safe languages. For example the code would be fine in C, since
double + int is
double. In Haskell, the type of
+ dictates that both sides need to be of the same type.
To get out of the “I/O [..] witchcraft”, let’s write one that’s easier to understand. First, we write a function to read a line:
getListLine :: Read a => IO [a] getListLine = do line <- getLine return (map read (words line))
Then, we use that function to get those three lines, calculate the area and the volume, and then print both values:
main :: IO () main = do as <- getListLine bs <- getListLine [l, r] <- getListLine let (area, volume) = solve l r (zip as bs) print area print volume
This should be a lot easier to understand than the function provided by the challenge site.
You call something
bs or similar if you have a collection. You call something
l' if it’s based on
l. So in your original
solve the usual names would be
solve :: Int -> Int -> [Int] -> [Int] -> [Double] solve l r as bs = [ sum [f x / 1000.0 | x <- xs], sum [pi * (f x) ^ 2 / 1000.0 | x <- xs] ] where as' = map fromIntegral as bs' = map fromIntegral bs l' = fromIntegral l r' = fromIntegral r xs = [l', l'+0.001..r'] f x = poly x as' bs'