Haskell在线平均

时间:2018-07-30 23:33:07

标签: haskell

我发现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

所以我的问题是:解决这个问题的最优雅的方法是什么?

1 个答案:

答案 0 :(得分:3)

单个Float不足以维持移动平均线,因此您的数据类型将必须拥有多个单个Float。一种明显的方法是用两个字段定义一个数据类型,即平均值和表示它代表多少个项目的计数:

data MovingAverage = MovingAverage Int Float

然后可以很容易地使用它们各自的平均值和项目计数来组合两个MovingAverage值:

instance SemiGroup MovingAverage where
  (MovingAverage n x) <> (MovingAverage m y) = -- ...

我将实现留空了,因为实际上有两个合理的实现:您可以

  1. 维护总和和除数,仅在需要时才计算实际平均值;
  2. 保持平均值和乘数,每次合并平均值时进行重构

(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