簡體   English   中英

如何使我的概率密度類型成為 Monoid 的一個實例?

[英]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
    }

VUData.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它開始於時間的開始,結束於時間的結束,並將這些無限年中的每一年的概率歸於零。 但這不能用未裝箱的向量來實現。

有沒有一種優雅的方法來制作CalPDFMonoid的實例? 用我已經擁有的讓它成為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 "+"

如果其中一個更適合您的需求,您可以以基本相同的方式使用HashMapIntMap代替Map

任何Semigroup都可以用Maybe提升為Monoid

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM