Если вы заботитесь о производительности и любите элегантный код, попробуйте
module MovingAverage =
let selfZip n l =
Seq.skip n l |> Seq.zip l
let runTotal i z =
Seq.scan ( fun sum (s, e) -> sum - s + e ) i z
let average n l:seq<'a> =
Seq.skip n l
|> selfZip n
|> runTotal (l |> Seq.take n |> Seq.sum)
|> Seq.map ( fun sum -> decimal sum / decimal n )
let ma = MovingAverage.average 2 myseq
Используя FSUnit, мы можем проверить его
let myseq = seq { for i in 0..10 do yield i }
Seq.nth 0 ma |> should equal 0.5
Seq.nth 1 ma |> should equal 1.5
Seq.nth 2 ma |> should equal 2.5
Seq.nth 3 ma |> should equal 3.5
Хитрость алгоритма заключается в первой сумме первых n чисел и
затем сохранить промежуточный итог, добавив заголовок окна
и вычитая хвост окна. Раздвижное окно
достигается путем самостоятельного застегивания на последовательности, но со вторым
аргумент для zip, продвинутый размером окна.
В конце конвейера мы просто делим промежуточную сумму на окно
размер.
Примечание: сканирование похоже на сгиб, но выдает каждую версию состояния в
последовательность.
Еще более элегантное решение, хотя возможно и с потерей производительности,
сделать замечание, что если мы обнуляем последовательность нам не нужна
рассчитать начальную сумму.
namespace Utils
module MovingAverage =
let selfZip n l =
Seq.skip n l |> Seq.zip l
let rec zeros =
seq { yield 0.0; yield! zeros}
// Create a running total given
let runTotal z =
Seq.scan (fun sum (s,e) -> sum - s + e ) 0.0 z
let average n l =
Seq.concat [(Seq.take n zeros); l]
|> selfZip n
|> runTotal
|> Seq.map ( fun sum -> sum / float n )
|> Seq.skip n
Возможна потеря производительности из-за второй косвенности, связанной с
упаковка двух последовательностей, но, возможно, это не имеет значения в зависимости
по размеру окна