scala 计算列表的移动平均线

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1319891/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:36:54  来源:igfitidea点击:

Calculating the Moving Average of a List

scalafunctional-programmingclojure

提问by James P

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 wanted to try out functional programming. This is where it got hard.

这个周末我决定尝试一些 Scala 和 Clojure。我精通面向对象编程,因此 Scala 很容易作为一门语言学习,但想尝试函数式编程。这就是它变得困难的地方。

I just can't seem to get my head into a mode of writing functions. As an expert functional programmer, how do you approach a problem?

我似乎无法进入编写函数的模式。作为专业的函数式程序员,您如何解决问题?

Given a list of values and a defined period of summation, how would you generate a new list of the simple moving average of the list?

给定一个值列表和一个定义的求和周期,您将如何生成列表的简单移动平均线的新列表?

For example: Given the list values(2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0), and the period4, the function should return: (0.0, 0.0, 0.0, 4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5)

例如:给定列表values(2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0) 和period4,函数应该返回: (0.0, 0.0, 0.0, 4.75, 5,0, 6.0) 7.25、8.0、8.25、6.5)

After spending a day mulling it over, the best I could come up with in Scala was this:

在花了一天的时间仔细考虑之后,我在 Scala 中能想到的最好的方法是:

def simpleMovingAverage(values: List[Double], period: Int): List[Double] = {
  (for (i <- 1 to values.length)
    yield
    if (i < period) 0.00
    else values.slice(i - period, i).reduceLeft(_ + _) / period).toList
}

I know this is horribly inefficient, I'd much rather do something like:

我知道这是非常低效的,我宁愿做这样的事情:

where n < period: ma(n) = 0
where n = period: ma(n) = sum(value(1) to value(n)) / period
where n > period: man(n) = ma(n -1) - (value(n-period) / period) + (value(n) / period)

Now that would be easily done in a imperative style, but I can't for the life of me work out how to express that functionally.

现在,以命令式风格很容易做到这一点,但我一生都无法弄清楚如何在功能上表达它。

采纳答案by Daniel C. Sobral

Interesting problem. I can think of many solutions, with varying degrees of efficiency. Having to add stuff repeatedly isn't really a performance problem, but let's assume it is. Also, the zeroes at the beginning can be prepended later, so let's not worry about producing them. If the algorithm provides them naturally, fine; if not, we correct it later.

有趣的问题。我能想到很多解决方案,效率各不相同。不得不重复添加东西并不是真正的性能问题,但让我们假设它是。此外,开头的零可以在以后添加,所以我们不用担心产生它们。如果算法自然地提供它们,那很好;如果没有,我们稍后更正。

Starting with Scala 2.8, the following would give the result for n >= periodby using slidingto get a sliding window of the List:

从 Scala 2.8 开始,以下将n >= period通过使用sliding获取列表的滑动窗口来给出结果:

def simpleMovingAverage(values: List[Double], period: Int): List[Double] =
  List.fill(period - 1)(0.0) ::: (values sliding period map (_.sum) map (_ / period))

Nevertheless, although this is rather elegant, it doesn't have the best performance possible, because it doesn't take advantage of already computed additions. So, speaking of them, how can we get them?

然而,虽然这相当优雅,但它并没有尽可能最好的性能,因为它没有利用已经计算的加法。那么,说到它们,我们如何才能得到它们呢?

Let's say we write this:

假设我们这样写:

values sliding 2 map sum

We have a list of the sum of each two pairs. Let's try to use this result to compute the moving average of 4 elements. The above formula made the following computation:

我们有一个每两对之和的列表。让我们尝试使用这个结果来计算 4 个元素的移动平均值。上述公式进行了以下计算:

from d1, d2, d3, d4, d5, d6, ...
to (d1+d2), (d2+d3), (d3+d4), (d4+d5), (d5+d6), ...

So if we take each element and add it to the second next element, we get the moving average for 4 elements:

因此,如果我们将每个元素添加到下一个元素中,我们将得到 4 个元素的移动平均值:

(d1+d2)+(d3+d4), (d2+d3)+(d4+d5), (d3+d4)+(d5+d6), ...

We may do it like this:

我们可以这样做:

res zip (res drop 2) map Function.tupled(_+_)

We could then compute the moving average for 8 elements, and so on. Well, there is a well known algorithm to compute things that follow such pattern. It's most known for its use on computing the power of a number. It goes like this:

然后我们可以计算 8 个元素的移动平均值,依此类推。嗯,有一个众所周知的算法来计算遵循这种模式的事物。它最出名的是用于计算数字的幂。它是这样的:

def power(n: Int, e: Int): Int = e match {
  case 0 => 1
  case 1 => n
  case 2 => n * n
  case odd if odd % 2 == 1 => power(n, (odd - 1)) * n
  case even => power(power(n, even / 2), 2)
}

So, let's apply it here:

所以,让我们在这里应用它:

def movingSum(values: List[Double], period: Int): List[Double] = period match {
  case 0 => throw new IllegalArgumentException
  case 1 => values
  case 2 => values sliding 2 map (_.sum)
  case odd if odd % 2 == 1 => 
    values zip movingSum(values drop 1, (odd - 1)) map Function.tupled(_+_)
  case even =>
    val half = even / 2
    val partialResult = movingSum(values, half)
    partialResult zip (partialResult drop half) map Function.tupled(_+_)
}

So, here's the logic. Period 0 is invalid, period 1 is equal to the input, period 2 is sliding window of size 2. If greater than that, it may be even or odd.

所以,这就是逻辑。周期 0 无效,周期 1 等于输入,周期 2 是大小为 2 的滑动窗口。如果大于该值,则可能是偶数或奇数。

If odd, we add each element to the movingSumof the next (odd - 1)elements. For example, if 3, we add each element to the movingSumof the next 2 elements.

如果是奇数,我们将每个元素添加到movingSum下一个(odd - 1)元素的 。例如,如果 3,我们将每个元素添加到movingSum接下来的 2 个元素中。

If even, we compute the movingSumfor n / 2, then add each element to the one n / 2steps afterwards.

如果偶数,我们计算movingSumfor n / 2,然后将每个元素添加到n / 2之后的一个步骤中。

With that definition, we can then go back to the problem and do this:

有了这个定义,我们就可以回到问题上来:

def simpleMovingAverage(values: List[Double], period: Int): List[Double] =
  List.fill(period - 1)(0.0) ::: (movingSum(values, period) map (_ / period))

There's a slight inefficiency with regards to the use of :::, but it's O(period), not O(values.size). It can be made more efficient with a tail recursive function. And, of course, the definition of "sliding" I provided is horrendous performance-wise, but there will be a much better definition of it on Scala 2.8. Note that we can't make an efficient slidingmethod on a List, but we can do it on an Iterable.

使用 时效率:::稍低,但它是 O(period),而不是 O(values.size)。使用尾递归函数可以提高效率。而且,当然,我提供的“滑动”的定义在性能方面是可怕的,但在 Scala 2.8 上会有更好的定义。请注意,我们无法sliding在 a 上创建有效的方法List,但我们可以在Iterable.

Having said all that, I'd go with the very first definition, and optimize only if a critical path analysis pinpointed this as a big deal.

说了这么多,我会采用第一个定义,并且只有在关键路径分析指出这是一个大问题时才进行优化。

To conclude, let's consider how I went about the problem. We have a moving average problem. A moving average is the sum of a moving "window" on a list, divided by the size of that window. So, first, I try to get a sliding window, sum everything on it, and then divide by the size.

最后,让我们考虑一下我是如何解决这个问题的。我们有一个移动平均问题。移动平均线是列表上移动“窗口”的总和除以该窗口的大小。因此,首先,我尝试获得一个滑动窗口,将其上的所有内容相加,然后除以大小。

The next problem was to avoid repetition of already computed additions. In this case, I went to the smallest addition possible, and tried to figure out how to compute bigger sums reusing such results.

下一个问题是避免重复已经计算的加法。在这种情况下,我尽可能使用最小的加法,并试图弄清楚如何重用这些结果来计算更大的总和。

Finally, let's try to solve the problem the way you figured it, by adding and subtracting from the previous result. Getting the first average is easy:

最后,让我们试着用你想象的方式解决这个问题,通过对之前的结果进行加法和减法。获得第一个平均值很容易:

 def movingAverage(values: List[Double], period: Int): List[Double] = {
   val first = (values take period).sum / period

Now we make two lists. First, the list of elements to be subtracted. Next, the list of elements to be added:

现在我们列出两个清单。首先,要减去的元素列表。接下来,要添加的元素列表:

   val subtract = values map (_ / period)
   val add = subtract drop period

We can add these two lists by using zip. This method will only produce as many elements as the smaller list has, which avoids the problem of subtractbeing bigger than necessary:

我们可以使用添加这两个列表zip。这种方法只会产生与较小列表一样多的元素,从而避免过subtract大的问题:

   val addAndSubtract = add zip subtract map Function.tupled(_ - _)

We finish by composing the result with a fold:

我们通过折叠组合结果来结束:

   val res = (addAndSubtract.foldLeft(first :: List.fill(period - 1)(0.0)) { 
     (acc, add) => (add + acc.head) :: acc 
   }).reverse

which is the answer to be returned. The whole function looks like this:

这是要返回的答案。整个函数如下所示:

 def movingAverage(values: List[Double], period: Int): List[Double] = {
   val first = (values take period).sum / period
   val subtract = values map (_ / period)
   val add = subtract drop period
   val addAndSubtract = add zip subtract map Function.tupled(_ - _)
   val res = (addAndSubtract.foldLeft(first :: List.fill(period - 1)(0.0)) { 
     (acc, add) => (add + acc.head) :: acc 
   }).reverse
   res
 }

回答by James Cunningham

I know Clojure better than Scala, so here goes. As I write this the other Clojure entry here is imperative; that's not really what you're after (and isn't idiomatic Clojure). The first algorithm that comes to my mind is repeatedly taking the requested number of elements from the sequence, dropping the first element, and recurring.

我比 Scala 更了解 Clojure,所以这里是。当我写这篇文章时,这里的另一个 Clojure 条目是必不可少的;这不是你真正想要的(也不是惯用的 Clojure)。我想到的第一个算法是重复从序列中获取所需数量的元素,删除第一个元素,然后重复。

The following works on any kind of sequence (vector or list, lazy or not) and gives a lazy sequence of averages---which could be helpful if you're working on a list of indefinite size. Note that it takes care of the base case by implicitly returning nil if there aren't enough elements in the list to consume.

以下适用于任何类型的序列(向量或列表,懒惰与否),并给出一个懒惰的平均值序列——如果您正在处理不确定大小的列表,这可能会有所帮助。请注意,如果列表中没有足够的元素可供使用,它会通过隐式返回 nil 来处理基本情况。

(defn moving-average [values period]
  (let [first (take period values)]
    (if (= (count first) period)
      (lazy-seq 
        (cons (/ (reduce + first) period)
              (moving-average (rest values) period))))))

Running this on your test data yields

在你的测试数据上运行这个会产生

user> (moving-average '(2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0) 4)
(4.75 5.0 6.0 7.25 8.0 8.25 6.5)

It doesn't give "0" for the first few elements in the sequence, though that could easily be handled (somewhat artificially).

它不会为序列中的前几个元素提供“0”,尽管这很容易处理(有点人为)。

The easiest thing of all is to see the pattern and be able to bring to mind an available function that fits the bill. partitiongives a lazy view of portions of a sequence, which we can then map over:

最简单的事情是查看模式并能够想到适合该法案的可用功能。partition给出序列部分的惰性视图,然后我们可以映射:

(defn moving-average [values period]
  (map #(/ (reduce + %) period) (partition period 1 values))

Someone asked for a tail recursive version; tail recursion vs. laziness is a bit of a tradeoff. When your job is building up a list then making your function tail recursive is usually pretty simple, and this is no exception---just build up the list as an argument to a subfunction. We'll accumulate to a vector instead of a list because otherwise the list will be built up backwards and will need to be reversed at the end.

有人要求尾递归版本;尾递归与懒惰是一种权衡。当您的工作是构建一个列表时,使您的函数尾递归通常非常简单,这也不例外——只需将列表构建为子函数的参数即可。我们将累积到一个向量而不是一个列表,否则列表将向后构建,并且需要在最后反转。

(defn moving-average [values period]
  (loop [values values, period period, acc []]
    (let [first (take period values)]
      (if (= (count first) period)
        (recur (rest values) period (conj acc (/ (reduce + first) period)))
        acc))))

loopis a way to make an anonymous inner function (sort of like Scheme's named let); recurmust be used in Clojure to eliminate tail calls. conjis a generalized cons, appending in the manner natural for the collection---the beginning of lists and the end of vectors.

loop是一种创建匿名内部函数的方法(有点像 Scheme 的命名 let);recur必须在 Clojure 中使用以消除尾调用。conj是一个广义的cons,以集合的自然方式附加——列表的开头和向量的结尾。

回答by Jonas

Here is another (functional) Clojure solution:

这是另一个(功能性)Clojure 解决方案:

(defn avarage [coll]
  (/ (reduce + coll)
     (count coll)))

(defn ma [period coll]
  (map avarage (partition period 1 coll)))

The zeros at the beginning of the sequence must still be added if that is a requirement.

如果需要,仍必须添加序列开头的零。

回答by Micha? Marczyk

Here's a purely functional solution in Clojure. More complex than those already provided, but it is lazyand only adjusts the average at each step, instead of recalculating it from scratch. It's actually slower than a simple solution which calculates a new average at each step if the period is small; for larger periods, however, it experiences virtually no slowdown, whereas something doing (/ (take period ...) period)will perform worse for longer periods.

这是 Clojure 中的纯函数式解决方案。比那些已经提供的更复杂,但它很懒惰只在每一步调整平均值,而不是从头开始重新计算。它实际上比一个简单的解决方案慢,如果周期很短,它会在每一步计算一个新的平均值;然而,在较长时期内,它几乎没有放缓,而在(/ (take period ...) period)较长时期内,某些事情的表现会更糟。

(defn moving-average
  "Calculates the moving average of values with the given period.
  Returns a lazy seq, works with infinite input sequences.
  Does not include initial zeros in the output."
  [period values]
  (let [gen (fn gen [last-sum values-old values-new]
              (if (empty? values-new)
                nil
                (let [num-out (first values-old)
                      num-in  (first values-new)
                      new-sum (+ last-sum (- num-out) num-in)]
                  (lazy-seq
                    (cons new-sum
                          (gen new-sum
                               (next values-old)
                               (next values-new)))))))]
    (if (< (count (take period values)) period)
      nil
      (map #(/ % period)
           (gen (apply + (take (dec period) values))
                (cons 0 values)
                (drop (dec period) values))))))

回答by Will

Here's a partially point-freeone line Haskell solution:

这是一个部分无点的单行 Haskell 解决方案:

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

First it applies tailsto 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):

反转它并删除第一个“p”条目(此处将 p 设为 2):

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/nipplesymbol, 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.

如果您不熟悉(.) 点/乳头符号,它是“功能组合”的运算符,这意味着它将一个函数的输出作为另一个函数的输入传递,将它们“组合”为一个函数。(g . f) 表示“对一个值运行 f 然后将输出传递给 g”,因此 ((f . g) x) 与 (g(fx)) 相同。通常它的使用会导致更清晰的编程风格。

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".

然后它将函数 ((/ (fromIntegral p)) . sum . take p) 映射到列表上。因此,对于列表中的每个列表,它采用第一个 'p' 元素,对它们求和,然后将它们除以 'p'。然后我们只需用“反向”再次翻转列表。

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 :)

这一切看起来比实际效率低得多;"reverse" 不会在物理上颠倒列表的顺序,直到列表被评估,它只是将它放在堆栈上(好 ol' 懒惰的 Haskell)。“tails”也不会创建所有这些单独的列表,它只是引用原始列表的不同部分。这仍然不是一个很好的解决方案,但它有一行长:)

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

这是一个更好但更长的解决方案,它使用 mapAccum 进行滑动减法和加法:

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:

首先,我们将列表在“p”处分成两部分,因此:

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). mapAccumLis 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.

现在我们为 mapAccum(ulator) 定义一个函数。mapAccumL与“map”相同,但有一个额外的运行状态/累加器参数,当 map 遍历列表时,该参数从前一个“映射”传递到下一个。我们使用累加器作为我们的移动平均线,因为我们的列表由刚刚离开滑动窗口的元素和刚刚进入它的元素(我们刚刚压缩的列表)组成,我们的滑动函数取第一个数字“x”远离平均值并添加第二个数字“y”。然后我们传递新的“s”并返回“s”除以“p”。“snd”(第二个)只取pair(元组)的第二个成员,用于取mapAccumL的第二个返回值,

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

对于那些不熟悉$ 符号的人,它是“应用程序运算符”。它实际上并没有做任何事情,但它具有“低,右结合绑定优先级”,因此这意味着您可以省略括号(注意 LISPers),即 (fx) 与 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.

运行 (ma 4 [2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0]) 产生 [4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 任一解决方案 6.5]

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

哦,您需要导入模块“List”来编译任一解决方案。

回答by Walter Chang

Here are 2 more ways to do moving average in Scala 2.8.0(one strict and one lazy). Both assume there are at least pDoubles in vs.

这里有另外 2 种在 Scala 2.8.0 中进行移动平均的方法(一种是严格的,一种是惰性的)。两者都假设vs中至少有p 个双打。

// strict moving average
def sma(vs: List[Double], p: Int): List[Double] =
  ((vs.take(p).sum / p :: List.fill(p - 1)(0.0), vs) /: vs.drop(p)) {(a, v) =>
    ((a._1.head - a._2.head / p + v / p) :: a._1, a._2.tail)
  }._1.reverse

// lazy moving average
def lma(vs: Stream[Double], p: Int): Stream[Double] = {
  def _lma(a: => Double, vs1: Stream[Double], vs2: Stream[Double]): Stream[Double] = {
    val _a = a // caches value of a
    _a #:: _lma(_a - vs2.head / p + vs1.head / p, vs1.tail, vs2.tail)
  }
  Stream.fill(p - 1)(0.0) #::: _lma(vs.take(p).sum / p, vs.drop(p), vs)
}

scala> sma(List(2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0), 4)
res29: List[Double] = List(0.0, 0.0, 0.0, 4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5)

scala> lma(Stream(2.0, 4.0, 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0), 4).take(10).force
res30: scala.collection.immutable.Stream[Double] = Stream(0.0, 0.0, 0.0, 4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5)

回答by kaleidic

The J programming language facilitates programs such as moving average. Indeed, there are fewer characters in (+/ % #)\than in their label, 'moving average.'

J 编程语言简化了诸如移动平均线之类的程序。事实上,里面的字符(+/ % #)\比他们标签中的要少,“移动平均”。

For the values specified in this question (including the name 'values') here is a straightforward way to code this:

对于此问题中指定的值(包括名称“值”),这是一种直接的编码方法:

   values=: 2 4 7 6 3 8 12 9 4 1
   4 (+/ % #)\ values
4.75 5 6 7.25 8 8.25 6.5

We can describe this by using labels for components.

我们可以通过对组件使用标签来描述这一点。

   periods=: 4
   average=: +/ % #
   moving=: \

   periods average moving values
4.75 5 6 7.25 8 8.25 6.5

Both examples use exactly the same program. The only difference is the use of more names in the second form. Such names can help readers who don't know the J primaries.

两个示例都使用完全相同的程序。唯一的区别是在第二种形式中使用了更多的名称。这样的名字可以帮助不知道J初选的读者。

Let's look a bit further into what's going on in the subprogram, average. +/denotes summation (Σ) and %denotes division (like the classical sign ÷). Calculating a tally (count) of items is done by #. The overall program, then, is the sum of values divided by the tally of values: +/ % #

让我们进一步了解子程序average. +/表示求和 (Σ) 并%表示除法(如经典符号 ÷)。计算项目的计数(计数)由 完成#。那么,整个程序是值的总和除以值的总数:+/ % #

The result of the moving-average calculation written here does not include the leading zeros expected in the original question. Those zeros are arguably not part of the intended calculation.

此处写入的移动平均计算结果不包括原始问题中预期的前导零。这些零可以说不是预期计算的一部分。

The technique used here is called tacit programming. It is pretty much the same as the point-free style of functional programming.

这里使用的技术称为默认编程。它与函数式编程的无点风格几乎相同。

回答by Jonathan Tran

Here is Clojure pretending to be a more functional language. This is fully tail-recursive, btw, and includes leading zeroes.

这是 Clojure 假装是一种功能性更强的语言。这是完全尾递归的,顺便说一句,包括前导零。

(defn moving-average [period values]
  (loop [[x & xs]  values
         window    []
         ys        []]

    (if (and (nil? x) (nil? xs))
      ;; base case
      ys

      ;; inductive case
      (if (< (count window) (dec period))
        (recur xs (conj window x) (conj ys 0.0))
        (recur xs
               (conj (vec (rest window)) x)
               (conj ys (/ (reduce + x window) period)))))))

(deftest test-moving-average
  (is (= [0.0 0.0 0.0 4.75 5.0 6.0 7.25 8.0 8.25 6.5]
         (moving-average 4 [2.0 4.0 7.0 6.0 3.0 8.0 12.0 9.0 4.0 1.0]))))

Usually I put the collection or list parameter last to make the function easier to curry. But in Clojure...

通常我把 collection 或 list 参数放在最后,使函数更容易柯里化。但是在 Clojure 中...

(partial moving-average 4)

... is so cumbersome, I usually end up doing this ...

......太麻烦了,我通常最终会这样做......

#(moving-average 4 %)

... in which case, it doesn't really matter what order the parameters go.

...在这种情况下,参数的顺序并不重要。

回答by John Lawrence Aspden

Here's a clojure version:

这是一个clojure版本:

Because of the lazy-seq, it's perfectly general and won't blow stack

由于惰性序列,它是完全通用的,不会吹堆栈

(defn partialsums [start lst]
  (lazy-seq
    (if-let [lst (seq lst)] 
          (cons start (partialsums (+ start (first lst)) (rest lst)))
          (list start))))

(defn sliding-window-moving-average [window lst]
  (map #(/ % window)
       (let [start   (apply + (take window lst))
             diffseq (map   - (drop window lst) lst)]
         (partialsums start diffseq))))

;; To help see what it's doing:

;; 为了帮助了解它在做什么:

(sliding-window-moving-average 5 '(1 2 3 4 5 6 7 8 9 10 11))

start = (+ 1 2 3 4 5) = 15

diffseq = - (6 7 8 9 10 11)
            (1 2 3 4  5  6 7 8 9 10 11)

        =   (5 5 5 5  5  5)

(partialsums 15 '(5 5 5 5 5 5) ) = (15 20 25 30 35 40 45)

(map #(/ % 5) (20 25 30 35 40 45)) = (3 4 5 6 7 8 9)

;; Example

;; 例子

(take 20 (sliding-window-moving-average 5 (iterate inc 0)))

回答by mikera

A short Clojure version that has the advantage of being O(list length) regardless of your period:

一个简短的 Clojure 版本,其优点是 O(列表长度),无论您的时间如何:

(defn moving-average [list period]
  (let [accums (let [acc (atom 0)] (map #(do (reset! acc (+ @acc %1 ))) (cons 0 list)))
        zeros (repeat (dec period) 0)]
     (concat zeros (map #(/ (- %1 %2) period) (drop period accums) accums))))

This exploits the fact that you can calculate the sum of a range of numbers by creating a cumulative sum of the sequence (e.g. [1 2 3 4 5] -> [0 1 3 6 10 15]) and then subtracting the two numbers with an offset equal to your period.

这利用了这样一个事实,即您可以通过创建序列的累积总和(例如 [1 2 3 4 5] -> [0 1 3 6 10 15])然后将两个数字相减来计算一系列数字的总和与您的期间相等的偏移量。