In Haskell, how do I get an average float from a list of integers?

天涯浪子 提交于 2020-05-17 07:44:09

问题


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 Ints in Haskell, you first have to convert them to Floats. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!