Bercovici Adrian
Bercovici Adrian

Reputation: 9360

Decomposing type as seed in fold

I have the following problem:

I want to calculate the sum of the first n numbers and keep the count of each added number on every iteration. Therefore i defined a type:

data Avg = Avg { sum :: Int, count :: Int }

I need to use a seed of type Avg in a foldl' but i need it decomposed inside the aggregator function:

bang :: [Int] -> IO ()
bang ls@(x:xs) = printAvg $ foldl ' (\x y -> (x sum+y count+1) ) (Avg 0 0) ls

printAvg :: Avg -> IO ()
printAvg av = putStrLn . show (fromIntegral $ sum av / fromIntegral $ count av)

So my question is:

Given a type data T = T { a :: Int, b :: Int } and given a variable myvar of type T, how can I place it for pattern matching instead of its data constructor?

In my example the foldl' takes an Avg which is the seed and one element from the list.

I need (\x y-> (x sum+y count+1)) instead of (\x y-> (Avg sum+y count+1)).

Upvotes: 1

Views: 84

Answers (2)

sshine
sshine

Reputation: 16105

Since data Avg = Avg { sum :: Int, count :: Int } is isomorphic to (Int, Int), you could also fold with a tuple:

average :: Fractional r => [Int] -> r
average = uncurry (/) . foldr (\x (sum, count) -> (sum+x, count+1)) (0,0)

bang :: [Int] -> IO ()
bang = putStrLn . show . average

And if you want to keep the average running, you could use a newtype wrapper:

newtype Count = Count (Int, Int)

accumulate :: [Int] -> Count
accumulate = foldr accum (Count (0, 0))
  where
    accum :: Int -> Count -> Count
    accum x (Count (sum, count)) = Count (sum+x, count+1)

average :: Fractional r => Count -> r
average (Count (x, y)) = x / y

bang :: [Int] -> IO ()
bang = putStrLn . show . average . accumulate

You might risk overflows in both cases.

Consider finding a moving average (Haskell).

Upvotes: 1

chi
chi

Reputation: 116139

A few possible solutions:

(\ (Avg s c) y -> Avg (s + y) (c + 1))
-- equivalent to the longer
(\ x y -> case x of Avg s c -> Avg (s + y) (c + 1))

-- mentioning the fields name explicitly
(\ Avg{sum=s, count=c} y -> Avg (s + y) (c + 1))

-- using the RecordWildCards extension
(\ Avg{..} y -> Avg (sum + y) (count + 1))

-- using the two projections
(\ x y -> Avg (sum x + y) (count x + 1))

or even, adapting your code

bang::[Int]->IO()
bang ls@(x:xs) = printAvg $ foldl' foo (Avg 0 0) ls
   where
   foo (Avg s c) y = Avg (s + y) (c+ 1)

(using let foo .. in .. is also possible)

Upvotes: 2

Related Questions