Reputation:
I think my code to find the mean of a list (of integers) works ok, but has a problem. This is my code
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))
my mean function has to go through the list twice. Once to get the sum of the elements, and once to get the number of elements. Obviously this is not great.
I would like to know a more efficient way to do this (using elementary Haskell - this is a a question from Real World Haskell chapter 3.)
Upvotes: 0
Views: 4078
Reputation: 11923
Now, there's an idea: a function that takes a bunch of monoids and applies each to a list, all at the same time. But that can come later!
I think what you'll need is a fold and a tuple for a good speed:
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
I've tried this out, and it's decently speedy in GHCi, though It may not be the best. I thought up a recursive method, too, though it requires a helper function:
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)
Then again, that's really slow. Painfully slow.
Looking at your solution, that's alright, but it's not really idiomatic Haskell. if
statements within functions tend to work better as pattern matches, especially if the Eq
class instance isn't defined for such-and-such a datatype. Furthermore, as my example illustrated, folds are beautiful! They allow Haskell to be lazy and therefore are faster. That's my feedback and my suggestion in response to your question.
Upvotes: 0
Reputation: 152857
I like the other answers here. But I don't like that they write their recursion by hand. There are lots of ways to do this, but one handy one is to reuse the Monoid
machinery we have in place.
Data.Monoid Data.Foldable> foldMap (\x -> (Sum x, Sum 1)) [15, 17, 19]
(Sum {getSum = 51}, Sum {getSum = 3})
The first part of the pair is the sum, and the second part is the length (computed as the sum of as many 1
s as there are elements in the list). This is a quite general pattern: many statistics can actually be cast as monoids; and pairs of monoids are monoids; so you can compute as many statistics about a thing as you like in one pass using foldMap
. You can see another example of this pattern in this question, which is where I got the idea.
Upvotes: 6
Reputation: 52039
What @simonzack is alluding to is that you should write listlen
and sumx
as folds.
Here is listlen
written as a fold:
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
Here s
is an accumulator which is passed from one iteration of the helper function go
to the next iteration. It is the value returned when the end of the list has been reached.
Writing sumx
as a fold will look like:
sumx :: [a] -> Int
sumx xs = go 0 xs
where go s [] = s
go s (a:as) = go ... as -- flll in the blank ...
The point is that given two folds you can always combine them so they are computed together.
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
Now you have computed both functions with one traversal of the list.
Upvotes: 2
Reputation: 18567
Define a helper function that only goes through once:
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)
Upvotes: 1