【问题标题】:Moving average in HaskellHaskell 中的移动平均线
【发布时间】:2017-03-19 19:08:54
【问题描述】:

给定权重列表:

let weights = [0.1, 0.2, 0.4, 0.2, 0.1]

还有一组测量值,我想实现加权平均。

这就是我在 Python 中的做法:

y=[]
w = length(weights)
for n in range(w,len(x)-w):
    y[n-w/2-1]=sum([a*b for a,b in zip(weights,x[n-w/2:n+w/2+1])])
    #y[n-3]=W[1]*x[n-2]+W[2]*x[n-1]+W[3]*x[n]+W[4]*x[n+1]+W[5]*x[n+2]

我知道 Haskell 没有数组,我想要实现的是 low-pass-filter,我可以在其中手动定义权重。

【问题讨论】:

  • 给点魔法,我想要一匹小马。但严重的是,这被严重低估了。 x 是什么?什么是w(也许你的意思是weight)? length w 总是 5 吗?你有其他语言的参考实现吗? 你尝试过什么?
  • X 是输入数组,w 是权重,y 是结果数组
  • 鉴于我们使用的是一种没有数组的语言,我假设您是语言初学者。无论哪种方式,附加信息都应该edited 到问题本身中,这样其他用户就不必从 cmets 中清除信息。我没有时间给出一个完整的答案,但这应该是可行的tails、zipWith 和一点边界摆弄。
  • 我已将 解决方案添加到我在 python 中的问题中,如果您能取消投票,我将不胜感激

标签: arrays haskell moving-average weighted-average


【解决方案1】:

tails 为您提供输入列表尾部的列表。所以tails [1,2,3] = [[1,2,3],[2,3],[3],[]]。由于我们不需要最后一个空列表,因此我们使用(init.tails) 来获取尾部列表中除最后一个元素之外的所有内容。

import Data.List (tails)
averages :: Num a => [a] -> [a] -> [a]
averages weights xs = sum . zipWith (*) weights <$> (init.tails) xs

请注意,这很可能在列表的开头和结尾不符合您的要求。特别是因为它在开始时和结束时的行为不同。第一个元素将是第一个 length weight 元素的平均值,但最后一个元素将只是 head weight * last xs。

如果你想要在开始时结束的行为,你可以使用这样的东西:

import Data.List (tails)
averages :: Num a => [a] -> [a] -> [a]
averages weights xs = sum . zipWith (*) weights <$>
  (init.tails) (replicate (length weights - 1) 0 ++ xs)

如果你想要在开始时结束的行为,你可以使用这个:

import Data.List (tails)
averages :: Num a => [a] -> [a] -> [a]
averages weights xs = sum . zipWith (*) weights <$>
  takeWhile (not . null . drop (l-1)) (tails xs)
  where l = length weights

如果您想以第一个/最后一个元素与权重列表的中心元素相乘开始和结束,我们必须使用上述两个答案的组合:

import Data.List (tails)
averages :: Num a => [a] -> [a] -> [a]
averages weights xs = sum . zipWith (*) weights <$>
  takeWhile (not . null . drop half) (replicate half 0 ++ xs)
  where half = length weights `quot` 2

【讨论】:

  • takeWhile (&gt;= length weights) 对资源不是很友好。相反,使用l = length weights 一次,然后使用not . null . drop (l - 1) 检查列表是否至少包含l 元素。
【解决方案2】:

压缩会自动进行对齐:

wma :: Num a => [a] -> [a] -> [a]
wma weights = map (sum . zipWith (*) weights )   -- weighted-moving-average
                . foldr (zipWith (:)) (repeat []) 
                . take (length weights) 
                . tails 

(see also).

【讨论】:

    【解决方案3】:

    移动平均线可以用mealy机器计算,其中内部状态是以前的值。

    我将展示三个参数的移动平均线示例,您可以自行调整,例如使其大小可参数化。

    Mealy machine本质上是一个初始状态,“状态+输入”到“新状态+输出”的功能:

    Mealy i o ~ (s, s -> i -> (o, s))
    

    让我们假设初始状态全为零,并编写一个用于 3 上移动平均的函数。

    type S = (Double, Double)
    type I = Double
    type O = Double
    
    initialState :: S
    initialState = (0, 0)
    
    weight0, weight1, weight2 :: Double
    weight0 = 0.25
    weight1 = 0.5
    weight2 = 0.25
    
    ma :: S -> I -> (O, S)
    ma (x0, x1) x2 = (o, s)
        where
        s = (x1, x2)
        o = x0 * weight0 + x1 * weight1 + x2 * weight2
    

    现在我们得到了所有的部分,让我们在输入上运行机器:

    runMealy :: (S -> I -> (O, S)) -> S -> [I] -> [O]
    runMealy _ _ [] = []
    runMealy f s (x : xs) =
        let (o, s') = f s x
        in o : runMealy f s' xs
    

    试试看:

    λ *Main > runMealy ma initialState [1,2,3,4,5,6,7,8,9]
    [0.25,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0]
    

    您可以删除第一个生成的值,因为机器内部状态正在“预热”。


    对于任意大小的移动平均机,您可以使用Data.Sequence,因为当您推送到一端,然后从另一端弹出时,它的数据结构要好得多,然后单链表,[] .


    为什么我要谈论 Mealy 机器?因为在某些时候,您很可能会遇到需要在 Haskell 中使用某些流式库的情况:pipes、conduit 或 machines。那么Mealy机器方法将是唯一合理的解决方案。

    您也可以制作自回归模型!

    【讨论】:

    • “那么 Mealy 机器方法将是唯一合理的解决方案”——这是一个相当有力的主张。你有参考吗?
    • @user3237465 如果您当时限制自己处理单个输入元素,您最终会得到类似 Mealy 机器的东西。可能有一些组合器为您制作移动缓冲区,但它本身就是 Mealy 机器。
    猜你喜欢
    • 2017-09-01
    • 2013-12-22
    • 2022-01-26
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 2019-04-24
    • 2012-05-24
    相关资源
    最近更新 更多