如何使我的概率密度类型成为 Monoid 的实例?
How to make my probability density type an instance of Monoid?
我有一个类型可以描述post-calibration radiocarbon date probability distributions。问题的细节和背景无关紧要:它归结为 _calPDFCals
中每年 _calPDFDens
中的一个概率值:
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
--
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
成为 <>
.
我无法克服并导致我 post 的问题是 mempty
应该是什么样子。我在 combinePDFs
: emptyBackdrop
中已经有了这样的东西。这在我的实施中是必需的,如果两个输入 PDF 之间不重叠 fill 或 complete 年。
emptyBackdrop
满足 mempty
的一些要求,但它取决于输入的 PDF。从理论上讲,真正的 mempty
应该是一个 CalPDF
,它从时间的开始开始,到时间的结束结束,并且将这些无限年中的每一年都归为零概率。但这不能用未装箱的向量来实现。
有没有一种优雅的方法来制作 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
任何 Semigroup
都可以用 Maybe
提升到 Monoid
。
考虑稍微修改一下您的类型。
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
时您不需要避免添加 +
以保持 law-abiding。 Pretty-printers 仍然可以用 +
s 显示这个,例如intercalate "+"
.
您可以使用 HashMap
或 IntMap
代替 Map
,如果其中之一更符合您的需求,则基本相同。
我有一个类型可以描述post-calibration radiocarbon date probability distributions。问题的细节和背景无关紧要:它归结为 _calPDFCals
中每年 _calPDFDens
中的一个概率值:
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
--
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
成为 <>
.
我无法克服并导致我 post 的问题是 mempty
应该是什么样子。我在 combinePDFs
: emptyBackdrop
中已经有了这样的东西。这在我的实施中是必需的,如果两个输入 PDF 之间不重叠 fill 或 complete 年。
emptyBackdrop
满足 mempty
的一些要求,但它取决于输入的 PDF。从理论上讲,真正的 mempty
应该是一个 CalPDF
,它从时间的开始开始,到时间的结束结束,并且将这些无限年中的每一年都归为零概率。但这不能用未装箱的向量来实现。
有没有一种优雅的方法来制作 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
任何 Semigroup
都可以用 Maybe
提升到 Monoid
。
考虑稍微修改一下您的类型。
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
时您不需要避免添加 +
以保持 law-abiding。 Pretty-printers 仍然可以用 +
s 显示这个,例如intercalate "+"
.
您可以使用 HashMap
或 IntMap
代替 Map
,如果其中之一更符合您的需求,则基本相同。