在Haskell中查找列表的平均值

时间:2014-11-29 04:53:26

标签: haskell recursion

我认为我的代码找到列表(整数)的平均值可以正常工作,但是有问题。这是我的代码

listlen xs = if null xs
             then 0
             else 1 + (listlen (tail xs))

sumx xs = if null xs
         then 0
         else (head xs) + sumx (tail xs)

mean xs = if null xs
          then 0
          else (fromIntegral (sumx xs)) / (fromIntegral (listlen xs))

我的平均功能必须经过两次列表。一旦得到元素的总和,一旦得到元素的数量。显然这不是很好。

我想知道一种更有效的方法(使用基本的Haskell - 这是来自真实世界Haskell 第3章的问题。)

4 个答案:

答案 0 :(得分:5)

我喜欢这里的其他答案。但我不喜欢他们手工写下他们的递归。有很多方法可以做到这一点,但一个方便的方法是重用我们现有的Monoid机制。

Data.Monoid Data.Foldable> foldMap (\x -> (Sum x, Sum 1)) [15, 17, 19]
(Sum {getSum = 51}, Sum {getSum = 3})

该对的第一部分是总和,第二部分是长度(计算为列表中有元素的1的总和)。这是一个非常普遍的模式:许多统计数据实际上可以被视为幺半群;和一对幺半群是幺半群;因此,您可以使用foldMap在一次传递中计算任意数量的事物统计信息。您可以在this question中看到此模式的另一个示例,这是我明白的地方。

答案 1 :(得分:2)

@simonzack暗示的是你应该将listlensumx写成 folds

以下listlen写为折叠:

listlen :: [a] -> Int
listlen xs = go 0 xs                 -- 0 = initial value of accumulator
  where go s [] = s                  -- return accumulator
        go s (a:as) = go (s+1) as    -- compute the next value of the accumulator
                                     -- and recurse

这里s是一个累加器,它从辅助函数go的一次迭代传递到下一次迭代。它是在到达列表末尾时返回的值。

sumx写为折叠将如下所示:

sumx :: [a] -> Int
sumx xs = go 0 xs
  where go s [] = s
        go s (a:as) = go ... as      -- flll in the blank ...

关键在于给定两个折叠,你总是可以将它们组合起来,这样它们就可以一起计算。

lenAndSum :: [a] -> (Int,Int)
lenAndSum xs = go (0,0) xs             -- (0,0) = initial values of both accumulators
  where go (s1,s2) [] = (s1,s2)        -- return both accumulators at the end
        go (s1,s2) (a:as) = go ... as  -- left as an exercise

现在你已经通过一次遍历列表计算了两个函数。

答案 2 :(得分:1)

定义一个只经过一次的辅助函数:

lengthAndSum xs = if null xs
                  then (0,0)
                  else let (a,b) = lengthAndSum(tail xs) in (a + 1, b + head xs)

mean xs = let (a, b) = lengthAndSum xs in (fromIntegral b / fromIntegral a)

答案 3 :(得分:0)

现在,有一个想法:一个函数需要一堆monoid并将每个monoid应用于列表,所有这些都是同时进行的。但那可以晚点来!

我认为你需要的是一个折叠和一个快速的元组:

avg :: (Fractional a) => [a] -> a
avg []   = error "Cannot take average of empty list"
avg nums = let (sum,count) = foldr (\e (s,c) -> (s+e,c+1)) (0,0)  nums
            in sum / count

我已经尝试过了,而且它在GHCi中速度非常快,尽管它可能不是最好的。我也想到了一个递归方法,虽然它需要一个辅助函数:

avg :: (Fractional a) => [a] -> a
avg []     = error "Cannot take average of empty list"
avg nums = let (sum, count) = go nums
            in sum / count
    where go [] = (0,0)
          go (x:xs) = let (sum',count') =  go xs
                       in (sum' + x, count' + 1)

然后,那个真的慢。痛苦地慢。

看看你的解决方案,这没关系,但它并不是真正惯用的Haskell。函数中的if语句往往在模式匹配时更好地工作,特别是如果没有为这样的数据类型定义Eq类实例。此外,正如我的例子所示,折叠是美丽的!它们允许Haskell变得懒惰,因此更快。这是我的反馈和我对你的问题的回答。