Averaging a string of numbers

Hi beginners list, I want to take a string of numbers and return the average. It is a string because I expect the input to be from STDIN or a file. I would like it to handle infinite lists. Further, I create the Stats data structure because later I will add support for other statistics, like max, min, sum, prod, etc. The following code works, but I'm not sure it's the proper way to do things in Haskell. For instance, maybe I could use a fold method instead of the explicit recursion in getStats? Could you please let me know if anything looks clunky or could be improved (efficiency, aesthetics, etc)? Thank you -- avg.hs -- sm is sum, len is number of elements data Stats = Stats { sm :: Double, len :: Int } deriving Show getAverage :: String -> Double getAverage str = (sm s / fromIntegral (len s)) where s = (getStats . readNumbers . lines) str getStats :: [Double] -> Stats getStats [] = Stats 0.0 0 getStats [x] = Stats x 1 getStats (x:xs) = Stats (x + (sm s)) ((len s) + 1) where s = getStats xs readNumbers :: [String] -> [Double] readNumbers = map read -- end avg.hs And here's the results in ghci: *Main> :load avg.hs [1 of 1] Compiling Main ( avg.hs, interpreted ) Ok, modules loaded: Main. *Main> getAverage "0" 0.0 *Main> getAverage "1.4\n0.5\n85.2\n30.2\n-10" 21.46 *Main> getAverage "" NaN -- -Michael Wayne Goodman

On Sat, Dec 10, 2011 at 07:21:58PM -0800, goodman.m.w@gmail.com wrote:
Hi beginners list,
I want to take a string of numbers and return the average. It is a string because I expect the input to be from STDIN or a file. I would like it to handle infinite lists. Further, I create the Stats data structure because later I will add support for other statistics, like max, min, sum, prod, etc. The following code works, but I'm not sure it's the proper way to do things in Haskell. For instance, maybe I could use a fold method instead of the explicit recursion in getStats?
You could indeed implement getStats with a fold, but we can even do one better.
-- avg.hs -- sm is sum, len is number of elements data Stats = Stats { sm :: Double, len :: Int } deriving Show
Let's add a Monoid instance for Stats, which specifies how two Stats objects should be combined: instance Monoid Stats where mempty = Stats 0 0 mappend (Stats sm1 len1) (Stats sm2 len2) = Stats (sm1 + sm2) (len1 + len2) We also specify how to create a default Stats object: mkStats x = Stats x 1 Now getStats is simply: getStats :: [Double] -> Stats getStats = mconcat . map mkStats That is, create a default Stats object from each list element, then combine/summarize them all using the Monoid instance. Other than that your code looks good to me. -Brent

Thanks for the reply, Brent!
I have a question about the "later" I referred to:
On Sun, Dec 11, 2011 at 7:19 AM, Brent Yorgey
On Sat, Dec 10, 2011 at 07:21:58PM -0800, goodman.m.w@gmail.com wrote:
... later I will add support for other statistics, like max, min, sum, prod, etc.
Specifically, how max and min work with the Monoid implementation.
Let's add a Monoid instance for Stats, which specifies how two Stats objects should be combined:
instance Monoid Stats where mempty = Stats 0 0 mappend (Stats sm1 len1) (Stats sm2 len2) = Stats (sm1 + sm2) (len1 + len2)
Monoids are new to me, but I just read a bit about them in Real World Haskell, and what you have makes sense. mempty is the identity function of Stats, and mappend is the associative binary operator. And thus far these properties hold true for the definition of Stats. If I add entries for min and max, it seems like it could still be valid. Of course, for an empty list of numbers, whereas sum can be 0.0 and len can be 0, there is no corresponding value for max and min, so I make them Maybe Double with an initial value of Nothing, and modify your code as follows: data Stats = Stats { sm :: Double, mn, mx :: Maybe Double, len :: Int } deriving Show instance Monoid Stats where mempty = Stats 0.0 Nothing Nothing 0 mappend (Stats sm1 mn1 mx1 len1) (Stats sm2 mn2 mx2 len2) = Stats (sm1 + sm2) (min mn1 mn2) (max mx1 mx2) (len1 + len2) mkStats x = Stats x (Just x) (Just x) 1 This compiles and works for max, but min does not work. I tested in ghci, and: Prelude> min (Just 0.1) (Nothing) Nothing Prelude> max (Just 0.1) (Nothing) Just 0.1 Which explains the problem. Any idea for the solution? -- -Michael Wayne Goodman
participants (2)
-
Brent Yorgey
-
goodman.m.w@gmail.com