使用折叠(Haskell)在列表中平均年龄

时间:2019-03-07 02:06:56

标签: haskell

我在学习时正在Haskell进行一些任意操作,并且一直在玩一系列具有某些属性(包括年龄)的动物。

这是我的脚本:

module Animals where

data Animal = CatThing String Int
            | DogThing String Int
            deriving Show

animalList :: [Animal]
animalList = [CatThing "Spot" 2, DogThing "Rex" 5]

-- write a function that returns the string component given an animal
getName :: Animal -> String
getName (CatThing name _) = name
getName (DogThing name _) = name 

-- get the age of an animal (uses "map")
getAge :: Animal -> Int
getAge (CatThing _ age) = age
getAge (DogThing _ age) = age

-- sum age
sumAge :: Int -> [Int] -> Int
sumAge _ [b, c] = foldl (+) 0 [b, c]

-- average age
???

我对如何使用foldl'求和感到困惑。我知道有一个内置的sum函数,但是我实际上是在尝试折叠,所以我尝试用这种方法。

有人对如何进行有建议吗?

2 个答案:

答案 0 :(得分:2)

您的总和代码看起来不错,我将使用foldl'而不是foldl,这样您就不会冒堆栈溢出的风险,也可以将[b,c]模式更改为通用模式可变甚至更好的自由点,因此它看起来更好,也更通用:

sumAge :: [Double] -> [Double]
sumAge = foldl' (+) 0

对于平均值,您只求和除以长度:

averageAge :: [Double] -> Double
averageAge ls = sumAge ls / length ls

PS。如果您的年龄是整数,则第一个函数仍然有效,但平均水平需要更改:

averageInt :: [Int] -> Double
averageInt ls = (fromInteger . sum) ls / (fromInteger . length) ls

答案 1 :(得分:2)

TL; DR版本

  • 总和:sumAges animals = foldl (\age animal -> age + (getAge animal)) 0 animals
  • 平均水平:
import Data.Sequence(foldlWithIndex, fromList)

average numbers = foldlWithIndex (\a i x -> let k = fromIntegral i in (k*a + x) / (k + 1)) 0 . fromList $ numbers

长版

如果您对数学感兴趣,那么可以理解折叠函数的设计,等同于通过归纳发现序列公式。

总和

总而言之,由于您拥有s[i+1] = s[i] + x[i+1],因此您可以像以前一样简单地使用加法,尽管在添加之前可能必须进行转换:

sumAges :: [Animal] -> Int
sumAges animals = foldl (\age animal -> age + (getAge animal)) 0 animals

sumAgesPointFree :: [Animal] -> Int
sumAgesPointFree = foldl (flip $ (+) . getAge) 0

平均值

例如,使用单个折叠函数计算列表平均值的一种方法是使用递归数学版本来计算序列的滚动平均值:m[i+1] = (i * m[i] + x[i+1]) / (i + 1)。您可以在计算大小不同的列表的平均值时看到这一点:

{-
  Not Haskell, just attempting mathematical notation without knowing MathML in Markdown.

  m: mean or average
  x: an element of a list or sequence
  []: subscript
 -}
m[1] = x[1]
m[2] = (x[1] + x[2]) / 2 = (m[1] + x[2]) / 2     -- m[1] = x[1], so substitute
m[3] = (x[1] + x[2] + x[3]) / 3                  -- (a+b)/n = a/n + b/n, so distribute
   = (x[1] + x[2]) / 3 + x[3] / 3                -- a = n/n * a, n not in {0, Infinity}
   = 2/2 * (x[1] + x[2]) / 3 + x[3] / 3          -- n/n * 1/a = n/a * 1/n
   = 2/3 * (x[1] + x[2]) / 2 + x[3] / 3          -- m[2] = (x[1] + x[2])/2, so substitute
   = 2/3 * m[2] + x[3] / 3
   = 2*m[2] / 3 + x[3] / 3
   = (2*m[2] + x[3]) / 3
...
m[i+1] = (i * m[i] + x[i+1]) / (i+1)

但是,由于此函数将需要元素索引作为参数,由于List结构缺少(方便的)索引,因此Data.Sequence模块的Sequence类型可能比List更好,尤其是考虑到Data.Sequence模块具有以下非常不错的foldlWithIndex函数:

module Average(average) where
  import Data.Sequence(foldlWithIndex, fromList)

  average :: Fractional a => [a] -> a
  average = foldlWithIndex averageByPrevious 0 . fromList
    where averageByPrevious previous index current = (coefficient*previous + current) / (coefficient + 1)
            where coefficient = fromIntegral index

然后,您可以简单地运行average list,其中list是要查找其滚动平均值的一些列表。这是一种使用一次折叠来计算列表平均值的方法,而不会像在同一个列表上运行多个O(n)函数那样添加较大的性能开销,甚至可以考虑将惰性作为对多次调用性能的好处。

注意:我承认,这不容易阅读,因此,如果此处的可读性比性能更重要,@ Lorenzo所说的average xs = (sum xs) / (length xs)会更好。