计算哈斯克尔的移动平均线

时间:2016-12-27 19:59:05

标签: haskell

我正在学习Haskell,所以我尝试实现移动平均功能。这是我的代码:

mAverage :: Int-> [Int] -> [Float]
mAverage x a = [fromIntegral k / fromIntegral x | k <- rawAverage]
    where
    rawAverage = mAverage' x a a

-- First list contains original values; second list contains moving average computations
mAverage' :: Int -> [Int] -> [Int] -> [Int]
mAverage' 1 a b = b
mAverage' x a b = mAverage' (x - 1) a' b'
    where
    a' = init a
    b' = zipWith (+) a' (tail b)

用户使用每个平均值的长度和值列表调用mAverage(例如mAverage 4 [1,2..100])。

然而,当我在输入mAverage 4 [1,2..100000]上运行代码时,我得到ghci需要3.6秒(使用:set +s)并使用一千兆字节的内存。这对我来说似乎效率很低,因为等效函数在Python中只需要几分之一秒。有什么办法可以让我的代码更有效率吗?

5 个答案:

答案 0 :(得分:9)

如果您想了解新的内容,可以查看移动平均值问题的优秀解决方案。它是由我的一个学生写的,所以我不会要求作者身份。我非常喜欢它,因为它很短。这里唯一的问题是average函数。已知这些功能很糟糕。相反,您可以使用Beautiful folds by Gabriel Gonzalez。是的,这个函数需要 O(k) 时间(其中k是窗口的大小)来计算窗口的平均值(我发现它更好,因为你可以如果您尝试仅将新元素添加到窗口并减去最后一个,则面临浮点错误。哦,它也使用State monad:)

{-# LANGUAGE UnicodeSyntax #-}

module MovingAverage where

import           Control.Monad       (forM)
import           Control.Monad.State (evalState, gets, modify)

moving :: Fractional a ⇒ Int → [a] → [a]
moving n _  | n <= 0 = error "non-positive argument"
moving n xs = evalState (forM xs $ \x → modify ((x:) . take (n-1)) >> gets average) []
  where
    average xs = sum xs / fromIntegral n

答案 1 :(得分:5)

这是一个简单的基于列表的解决方案,虽然需要更多内存,但却是惯用且足够快的。

ListModel listModel = listModels.get(position);

此解决方案允许在移动窗口中使用任何函数而不是import Data.List (tails) mavg :: Fractional b => Int -> [b] -> [b] mavg k lst = take (length lst-k) $ map average $ tails lst where average = (/ fromIntegral k) . sum . take k

以下解决方案不太普遍,但它在空间中是恒定的,似乎是最快的。

average

最后,使用一种Okasaki的持久功能队列来保持移动窗口的解决方案。在处理流数据时,例如管道或管道,它确实有意义。

import Data.List (scanl')

mavg :: Fractional b => Int -> [b] -> [b]
mavg k lst = map (/ fromIntegral k) $ scanl' (+) (sum h) $ zipWith (-) t lst
  where (h, t) = splitAt k lst 

正如在原始帖子的评论中提到的那样,不要使用mavg k lst = map average $ scanl' enq ([], take k lst) $ drop k lst where average (l,r) = (sum l + sum r) / fromIntegral k enq (l, []) x = enq ([], reverse l) x enq (l, (_:r)) x = (x:l, r) 进行性能分析。例如,您无法在ghci中看到scanl'的任何好处。

答案 2 :(得分:1)

这是您的解决方案。

这个想法是扫描两个列表,一个是平均窗口开始的,另一个是结束的列表。获取列表的尾部成本与扫描我们正在跳过的部分一样多,而且我们不会复制任何内容。 (如果窗口大小通常非常大,我们可以一次性计算remaining_data并计算sum initial_data。)

我们会根据评论中的描述生成一个部分和的列表,然后将它们除以窗口宽度以获得平均值。

虽然slidingAverage计算偏向位置的平均值(窗口宽度向右),centeredSlidingAverage计算居中平均值,使用左侧和右侧的半窗口宽度。

import Data.List (splitAt, replicate)

slidingAverage :: Int -> [Int] -> [Double] -- window size, source list -> list of averages
slidingAverage w xs = map divide $ initial_sum : slidingSum initial_sum xs remaining_data
  where
    divide = (\n -> (fromIntegral n) / (fromIntegral w))  -- divides the sums by window size
    initial_sum = sum initial_data
    (initial_data, remaining_data) = splitAt w xs

centeredSlidingAverage :: Int -> [Int] -> [Double] -- window size, source list -> list of averages
centeredSlidingAverage w xs = slidingAverage w $ left_padding ++ xs ++ right_padding
  where
    left_padding = replicate half_width 0
    right_padding = replicate (w - half_width) 0
    half_width = (w `quot` 2)   -- quot is integer division

slidingSum :: Int -> [Int] -> [Int] -> [Int] -- window_sum before_window after_window -> list of sums
slidingSum _ _ [] = []
slidingSum window_sum before_window after_window = new_sum : slidingSum new_sum new_before new_after
  where
    value_to_go = head before_window
    new_before = tail before_window
    value_to_come = head after_window
    new_after = tail after_window
    new_sum = window_sum - value_to_go + value_to_come

当我尝试length $ slidingAverage 10 [1..1000000]时,我的MBP只需不到一秒钟。 Due to the lazinesscenteredSlidingAverage大约需要同一时间。

答案 3 :(得分:0)

一种简单的方法,也使用O(n)复杂度

[user2@test ~]$ sudo su - user1
user2 Password:
[user1@test ~]$ sh script.sh

答案 4 :(得分:0)

另一种方法是使用STUArray。

import           Data.Array.Unboxed
import           Data.Array.ST
import           Data.STRef
import           Control.Monad
import           Control.Monad.ST

movingAverage  :: [Double] -> IO [Double]
movingAverage vals = stToIO $ do
  let end = length vals - 1
  myArray <- newArray (1, end) 0 :: ST s (STArray s Int Double)
  forM_ [1 .. end] $ \i -> do
    let cval = vals !! i
    let lval = vals !! (i-1)
    writeArray myArray i ((cval + lval)/2)
  getElems myArray