[英]How to make my probability density type an instance of Monoid?
我有一種類型來描述校准后放射性碳日期概率分布。 細節和背景對於這個問題並不重要:它歸結為_calPDFDens
中每一年在_calPDFCals
中的一個概率值:
data CalPDF = CalPDF {
-- | Sample identifier, e.g. a lab number
_calPDFid :: String
-- | Years calBCAD
, _calPDFCals :: VU.Vector YearBCAD
-- | Probability densities for each year in '_calPDFCals'
, _calPDFDens :: VU.Vector Float
}
( VU
是Data.Vector.Unboxed
)
現在:通常的做法是對多個此類分布求和以得出和概率分布。 這意味着對_calPDFCals
_calPDFDens
的各個值求和。 我實現了如下:
sumPDFs :: CalPDF -> CalPDF -> CalPDF
sumPDFs = combinePDFs (+)
combinePDFs :: (Float -> Float -> Float) -> CalPDF -> CalPDF -> CalPDF
combinePDFs f (CalPDF name1 cals1 dens1) (CalPDF name2 cals2 dens2) =
let startRange = minimum [VU.head cals1, VU.head cals2]
stopRange = maximum [VU.last cals1, VU.last cals2]
emptyBackdrop = zip [startRange..stopRange] (repeat (0.0 :: Float))
pdf1 = VU.toList $ VU.zip cals1 dens1
pdf2 = VU.toList $ VU.zip cals2 dens2
pdfCombined = fullOuter f pdf2 (fullOuter f pdf1 emptyBackdrop)
pdfNew = CalPDF (name1 ++ "+" ++ name2) (VU.fromList $ map fst pdfCombined) (VU.fromList $ map snd pdfCombined)
in normalizeCalPDF pdfNew
where
-- https://stackoverflow.com/questions/24424403/join-or-merge-function-in-haskell
fullOuter :: (Float -> Float -> Float) -> [(YearBCAD, Float)] -> [(YearBCAD, Float)] -> [(YearBCAD, Float)]
fullOuter _ xs [] = xs
fullOuter _ [] ys = ys
fullOuter f xss@(x:xs) yss@(y:ys)
| fst x == fst y = (fst x, f (snd x) (snd y)) : fullOuter f xs ys
| fst x < fst y = x : fullOuter f xs yss
| otherwise = y : fullOuter f xss ys
我想知道是否可以重寫此代碼,以便CalPDF
成為Monoid
的實例, sumPDFs
成為<>
。
我無法克服並導致我發帖的問題是, mempty
應該是什么樣子。 我已經在combinePDFs
中有這樣的東西: emptyBackdrop
。 這在我的實現中是必需的,以填充或完成兩個輸入 PDF 之間的年份(如果它們不重疊)。
emptyBackdrop
滿足mempty
的一些要求,但它取決於輸入的 PDF。 從理論上講,真正的CalPDF
mempty
它開始於時間的開始,結束於時間的結束,並將這些無限年中的每一年的概率歸於零。 但這不能用未裝箱的向量來實現。
有沒有一種優雅的方法來制作CalPDF
和Monoid
的實例? 用我已經擁有的讓它成為Semigroup
的實例是否有用?
編輯:正如@leftaroundabout 所建議的,這里是上述設置的可重現的最小實現。
main :: IO ()
main = do
let myPDF1 = [(1,1), (2,1), (3,1)]
myPDF2 = [(2,1), (3,1), (4,1)]
putStrLn $ show $ sumPDFs myPDF1 myPDF2
type CalPDF = [(Int, Float)]
sumPDFs :: CalPDF -> CalPDF -> CalPDF
sumPDFs pdf1 pdf2 =
let startRange = minimum [fst $ head pdf1, fst $ head pdf2]
stopRange = maximum [fst $ last pdf1, fst $ last pdf2]
emptyBackdrop = zip [startRange..stopRange] (repeat (0.0 :: Float))
pdfCombined = fullOuter pdf2 (fullOuter pdf1 emptyBackdrop)
in pdfCombined
where
fullOuter :: [(Int, Float)] -> [(Int, Float)] -> [(Int, Float)]
fullOuter xs [] = xs
fullOuter [] ys = ys
fullOuter xss@(x@(year1,dens1):xs) yss@(y@(year2,dens2):ys)
| year1 == year2 = (year1, dens1 + dens2) : fullOuter xs ys
| year1 < year2 = x : fullOuter xs yss
| otherwise = y : fullOuter xss ys
考慮修改一下你的類型。
import Data.Map (Map)
import qualified Data.Map as M
data CalPDF = CalPDF
{ _calPDFid :: [String]
, _calPDFdens :: Map YearBCAD Float
}
現在的實例確實可以很短:
instance Semigroup CalPDF where
CalPDF id dens <> CalPDF id' dens' = CalPDF
(id <> id')
(M.unionWith (+) dens dens')
instance Monoid CalPDF where
mempty = CalPDF mempty mempty
我使用[String]
代替單個+
+
String
有兩個原因:它允許您在名稱中使用+
而不會產生歧義,並且它使<>
更簡單一些,因為您不需要避免在一個或另一個參數是保持守法的空String
。 漂亮的打印機仍然可以使用+
s 來顯示這一點,例如intercalate "+"
。
如果其中一個更適合您的需求,您可以以基本相同的方式使用HashMap
或IntMap
代替Map
。
任何Semigroup
都可以用Maybe
提升為Monoid
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.