我发现this article涉及在线平均计算,我想将此代码转换为Haskell。我幼稚的想法是使用半群:
=(IsNothing(Fields!filepath.Value) = False) OR Parameters!STATUS.Value != "in process"
但是,半群人不记得自己的位置,因此不可能知道值import Data.Semigroup
newtype MovingAverage = MovingAverage { getMovingAverage :: Float }
instance Semigroup MovingAverage where
(MovingAverage a) <> (MovingAverage b) = MovingAverage (a+(a-b)*recip n) -- Variable not in scope: n
。
所以我的问题是:解决这个问题的最优雅的方法是什么?
答案 0 :(得分:3)
单个Float不足以维持移动平均线,因此您的数据类型将必须拥有多个单个Float。一种明显的方法是用两个字段定义一个数据类型,即平均值和表示它代表多少个项目的计数:
data MovingAverage = MovingAverage Int Float
然后可以很容易地使用它们各自的平均值和项目计数来组合两个MovingAverage值:
instance SemiGroup MovingAverage where
(MovingAverage n x) <> (MovingAverage m y) = -- ...
我将实现留空了,因为实际上有两个合理的实现:您可以
(1)如果您比查看平均值更频繁地组合平均值,那么它会更便宜(因为加法比除法和乘法要便宜),但是(2)如果您多次查询相同的平均值,则会更便宜,因为它避免了每次查询时都会进行划分。
在实践中,我通常希望(1)是最简单的方法,并且对于正常的工作负载而言表现相当不错。您可以尝试对(1)进行改进,这可能对移动平均值而言是过大的,但如果您尝试将此模式应用于更昂贵的计算,则可能会有意义。
{-# LANGUAGE BangPatterns #-}
import Data.Semigroup
data MovingAverage = MovingAverage { sum :: !Float,
count :: !Int,
getAverage :: Float
}
mkAverage :: Float -> MovingAverage
mkAverage x = MovingAverage x 1 x
instance Semigroup MovingAverage where
(MovingAverage x n _) <> (MovingAverage y m _) = MovingAverage quot div (quot / fromIntegral div)
where quot = x + y
div = n + m
与(1)相同,除了我们包括一个用于缓存除法结果的字段,该字段已写入但从未读取。因此,如果客户多次调用getAverage
相同的值,则只会执行一次除法;或者,如果他们从不叫它,那么根本就不需要除法,而我们可以用便宜的价格工作。不利的一面是,您的唱片更大,并且您花费时间分配许多没人看过的东西。就像我说过的那样,您可能只会这样做以缓存更昂贵的查询,同时您不确定该查询是否会更频繁地更新或更频繁地查询。
用法示例:
*Main Data.Semigroup> getAverage $ mkAverage 13 <> stimes 9 (mkAverage 3)
4.0