我认为我的代码找到列表(整数)的平均值可以正常工作,但是有问题。这是我的代码
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章的问题。)
答案 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暗示的是你应该将listlen
和sumx
写成 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变得懒惰,因此更快。这是我的反馈和我对你的问题的回答。