Reputation: 3589
I'm going through Learn You A Haskell and just finished the part "For a few monads more". In this part we created a newtype Prob a = Prob { getProb :: [(a, Rational)] }
and created a Monad
instance for it. This allows us to calculate probabilities of results in non-deterministic calculations like the following:
data Coin = Heads | Tails deriving (Show, Eq)
coin :: Prob Coin
coin = Prob [(Heads, 1%2), (Tails, 1%2)]
loadedCoin :: Prob Coin
loadedCoin = Prob [(Heads, 1%10), (Tails, 9%10)]
coinTest :: Prob Bool
coinTest = do
a <- coin
b <- coin
c <- loadedCoin
return (all (==Tails) [a,b,c])
Of course, this doesn't generate a very pretty result:
getProb coinTest
>> [(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]
It was left as an exercise to the reader to write a neat function to sum up all the False
s and all the True
s so we get [(True,9 % 40),(False,31 % 40)]
. I've managed to do this, sort of. It works for this particular case, but I feel like it isn't a useful function at all since it's so specialized. Here's what I came up with:
sumProbs :: Prob Bool -> Prob Bool
sumProbs (Prob ps) = let (trues, falses) = partition fst ps
ptrue = reduce trues
pfalse = reduce falses
in Prob [ptrue, pfalse]
where reduce = foldr1 (\(b,r) (_,r') -> (b,r+r'))
I'd love to generalize it to work for any Eq a => Prob a
, but so far no luck. I was thinking of maybe using a Map
together with unionWith
or something like that. Or maybe I can exploit the fact that (a,b)
has a Functor b
instance? I think I'm missing some simpler more elegant solution.
So, to sum it up: How can I write a function sumProbs :: (Eq a) => Prob a -> Prob a
that sums up all the probabilities that share the same value (key)?
Upvotes: 2
Views: 235
Reputation: 78001
If you use Data.Map
, then fromListWith
and toList
will do:
import Data.Map (toList, fromListWith)
newtype Prob a = Prob { getProb :: [(a, Rational)] }
deriving Show
sumProbs :: (Ord a) => Prob a -> Prob a
sumProbs = Prob . toList . fromListWith (+) . getProb
Relaxing Ord a
to Eq a
would require a less efficient quadratic computation; something like:
sumProbs :: (Eq a) => Prob a -> Prob a
sumProbs = Prob . foldr go [] . getProb
where
go (x, y) = run
where
run [] = (x, y):[]
run ((a, b):rest)
| x == a = (x, y + b): rest
| otherwise = (a, b): run rest
Upvotes: 3
Reputation: 120741
Using Map
is a good idea, but you'll need Ord a
in addition to Eq a
. If you're ok with that, then we can also do simpler list solution: just replace partition
with a combination of sortBy
and groupBy
:
import Data.List (groupBy, sortBy)
import Data.Function (on)
sumProbs :: (Ord a) => Prob a -> Prob a
sumProbs (Prob ps) = Prob . map reduce
. groupBy ((==)`on`fst)
$ sortBy (compare`on`fst) ps
Upvotes: 2