问题
I have written a simple function that displays: Name of place, Degrees North, Degrees East, and a list of rainfall numbers.
How do I get an average rainfall specific to a place? For example in my code, how do I get an average rainfall for London?
Sorry if my code is not the best, I'm just learning Haskell.
import Data.Char
import Data.List
type Place = (String, Float, Float, [Int])
testData :: [Place]
testData = [("London", 51.5, -0.1, [0, 0, 5, 8, 8, 0, 0]),
("Cardiff", 51.5, -3.2, [12, 8, 15, 0, 0, 0, 2]),
("Norwich", 52.6, 1.3, [0, 6, 5, 0, 0, 0, 3])]
rainLevels :: [Place] -> Float
rainLevels level (_, _, _, numbers) = sum numbers / 7
回答1:
To find the average rainfall for "London", you need to first find the record for "London" in your list, then average its rainfall numbers.
getAvgRainLevel
consumes a name of a specific city: name
, and a list of records: xs
; it finds the record of that city by its name
, then calculates the average rainfall level for that city.
getAvgRainLevel name xs = fmap avg $ lookup name (sanitize xs)
where
sanitize = fmap (\(city, _, _, rainLevels) -> (city, rainLevels))
avg = (/) <$> sum <*> fromIntegral . length
回答2:
If you want to divide Int
s in Haskell, you first have to convert them to Float
s. This avoids the problems you get with division in other statically typed languages like C, where 5/2
is 2
.
So, how do you convert an Int
to a Float
? There are a number of functions for doing this, but the most paradigmatic choice is fromIntegral
:
fromIntegral :: (Num b, Integral a) => a -> b
This will convert any Integral type to any Numeric type. Thus if you need to convert a [Int]
to a [Float]
:
fmap fromIntegral :: (Functor f, Num b, Integral a) => f a -> f b
There are another couple problems. Your code will not compile as-is. You have declared your function rainLevels
to only take one argument, but you pattern match on two arguments when you define it. As currently defined, the actual type of rainLevels
is:
rainLevels :: (Frac e) => a -> (b, c, d, [e]) -> e
Place
matches (b, c, d, [e])
, but not with the constraint (Frac e)
, which is what fromIntegral
will solve, but you'll still be left with the issue that [Place]
will not pattern match on (_, _, _, numbers)
. Since my mind-reading skills have sadly lapsed, I leave it to you to work out whether rainLevels
is meant to take one Place
or a list of them, but either way, you need to make the signature match the definition.
回答3:
In addition to the explanation of Andrew Ray, I thought it might be helpful to provide an working example in code:
module Lib
( avgRainLevel
, rainLevels
, testData
) where
import Data.Char
import Data.List
type Place = (String, Float, Float, [Int])
testData :: [Place]
testData = [("London", 51.5, -0.1, [0, 0, 5, 8, 8, 0, 0]),
("Cardiff", 51.5, -3.2, [12, 8, 15, 0, 0, 0, 2]),
("Norwich", 52.6, 1.3, [0, 6, 5, 0, 0, 0, 3])]
findPlace :: [Place] -> String -> Place
findPlace placeList place = head . filter isPlace $ placeList
where isPlace (name, _, _, _) = name == place
getLevel :: Place -> [Int]
getLevel (_, _, _, levels) = levels
rainLevels :: [Place] -> String -> [Int]
rainLevels placeList place = getLevel $ findPlace placeList place
average :: [Int] -> Float
average ints = fromIntegral levelSum / fromIntegral count
where levelSum = sum ints
count = length ints
avgRainLevel :: [Place] -> String -> Float
avgRainLevel placeList place = average $ rainLevels placeList place
I was a bit unsure what the actual value you want to get. From the name rainLevels
I concluded that you want the list of rain levels, so I adapted the type to [Int]
. Because you don't just provide the list of places, but also the actual place you want to query, I had to add another parameter which added a -> String
to the type declaration of the function.
Based on the function, I did another function avgRainLevel
that calculates the average of the values in the list of type [Int]
.
The code will work like documented with these tests:
import Lib (rainLevels, avgRainLevel, testData)
import Test.Hspec (Spec, it, shouldBe)
import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)
main :: IO ()
main = hspecWith defaultConfig {configFastFail = True} specs
specs :: Spec
specs = do
it "can read the list of levels" $ do
let levels = rainLevels testData "London"
levels `shouldBe` [0, 0, 5, 8, 8, 0, 0]
it "can calculate the average rain level" $ do
let avg = avgRainLevel testData "Norwich"
avg `shouldBe` 2
Please note, that actually you shouldn't return [Int]
or Float
from any of these two functions. The code as it is will panic when you query for a place, that is not present in the list of places. You should better return Maybe [Int]
and Maybe Float
. In that case you can return Nothing
when you have no information about a given place available.
来源:https://stackoverflow.com/questions/61365035/in-haskell-how-do-i-get-an-average-float-from-a-list-of-integers