Calculating the Moving Average of a List

后端 未结 18 1178
悲哀的现实
悲哀的现实 2020-12-07 09:01

This weekend I decided to try my hand at some Scala and Clojure. I\'m proficient with object oriented programming, and so Scala was easy to pick up as a language, but wante

18条回答
  •  时光说笑
    2020-12-07 09:45

    Here's a partially point-free one line Haskell solution:

    ma p = reverse . map ((/ (fromIntegral p)) . sum . take p) . (drop p) . reverse . tails
    

    First it applies tails to the list to get the "tails" lists, so:

    Prelude List> tails [2.0, 4.0, 7.0, 6.0, 3.0]
    [[2.0,4.0,7.0,6.0,3.0],[4.0,7.0,6.0,3.0],[7.0,6.0,3.0],[6.0,3.0],[3.0],[]]
    

    Reverses it and drops the first 'p' entries (taking p as 2 here):

    Prelude List> (drop 2 . reverse . tails) [2.0, 4.0, 7.0, 6.0, 3.0]
    [[6.0,3.0],[7.0,6.0,3.0],[4.0,7.0,6.0,3.0],[2.0,4.0,7.0,6.0,3.0]]
    

    In case you aren't familiar with the (.) dot/nipple symbol, it is the operator for 'functional composition', meaning it passes the output of one function as the input of another, "composing" them into a single function. (g . f) means "run f on a value then pass the output to g", so ((f . g) x) is the same as (g(f x)). Generally its usage leads to a clearer programming style.

    It then maps the function ((/ (fromIntegral p)) . sum . take p) onto the list. So for every list in the list it takes the first 'p' elements, sums them, then divides them by 'p'. Then we just flip the list back again with "reverse".

    Prelude List> map ((/ (fromIntegral 2)) . sum . take 2) [[6.0,3.0],[7.0,6.0,3.0]
    ,[4.0,7.0,6.0,3.0],[2.0,4.0,7.0,6.0,3.0]]
    [4.5,6.5,5.5,3.0]
    

    This all looks a lot more inefficient than it is; "reverse" doesn't physically reverse the order of a list until the list is evaluated, it just lays it out onto the stack (good ol' lazy Haskell). "tails" also doesn't create all those separate lists, it just references different sections of the original list. It's still not a great solution, but it one line long :)

    Here's a slightly nicer but longer solution that uses mapAccum to do a sliding subtraction and addition:

    ma p l = snd $ mapAccumL ma' a l'
        where
            (h, t) = splitAt p l
            a = sum h
            l' = (0, 0) : (zip l t)
            ma' s (x, y) = let s' = (s - x) + y in (s', s' / (fromIntegral p))
    

    First we split the list into two parts at "p", so:

    Prelude List> splitAt 2 [2.0, 4.0, 7.0, 6.0, 3.0]
    ([2.0,4.0],[7.0,6.0,3.0])
    

    Sum the first bit:

    Prelude List> sum [2.0, 4.0]
    6.0
    

    Zip the second bit with the original list (this just pairs off items in order from the two lists). The original list is obviously longer, but we lose this extra bit:

    Prelude List> zip [2.0, 4.0, 7.0, 6.0, 3.0] [7.0,6.0,3.0]
    [(2.0,7.0),(4.0,6.0),(7.0,3.0)]
    

    Now we define a function for our mapAccum(ulator). mapAccumL is the same as "map", but with an extra running state/accumulator parameter, which is passed from the previous "mapping" to the next one as map runs through the list. We use the accumulator as our moving average, and as our list is formed of the element that has just left the sliding window and the element that just entered it (the list we just zipped), our sliding function takes the first number 'x' away from the average and adds the second number 'y'. We then pass the new 's' along and return 's' divided by 'p'. "snd" (second) just takes the second member of a pair (tuple), which is used to take the second return value of mapAccumL, as mapAccumL will return the accumulator as well as the mapped list.

    For those of you not familiar with the $ symbol, it is the "application operator". It doesn't really do anything but it has a has "low, right-associative binding precedence", so it means you can leave out the brackets (take note LISPers), i.e. (f x) is the same as f $ x

    Running (ma 4 [2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0]) yields [4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5] for either solution.

    Oh and you'll need to import the module "List" to compile either solution.

提交回复
热议问题