Moving Average Calculation in Kotlin
I’m working on an app that calculates and displays moving averages for a list of numbers. This post shows how I’m doing it in Kotlin, using list operations instead of iteratively. The source code for this is available on GitHub.
Calculations
First I pull out all the sub-lists of the required size. For my purposes, I also want the short sublists at the start, of size 1, 2, …, n.
fun <T> List<T>.slidingWindow(size: Int): List<List<T>> {
if (size < 1) {
throw IllegalArgumentException("Size must be > 0, but is $size.")
}
return this.mapIndexed { index, _ ->
this.subList(maxOf(index - size + 1, 0), index + 1)
}
}
Next comes a couple of functions to calculate means, both standard, and weighted. These are extension functions in Iterable
fun Iterable<Float>.mean(): Float {
val sum: Float = this.sum()
return sum / this.count()
}
fun sumTo(n: Int): Int = n * (n + 1) / 2
fun Iterable<Float>.weightedMean(): Float {
val sum: Float = this
.mapIndexed { index, t -> t * (index + 1) }
.sum()
return sum / sumTo(this.count())
}
With these I can now calculate the moving averages. The following code pulls it all together.
fun movingAverage(entries: List<Float>, window: Int,
averageCalc: Iterable<Float>.() -> Float): List<Float> {
val result = entries.slidingWindow(size = window)
.filter { it.isNotEmpty() }
.map { it -> it.averageCalc() }
.toList()
return result
}
Unit Tests
And, of course, where would I be without unit tests?
object MovingAverageSpec : Spek({
given("A list of numbers") {
val input = arrayListOf(1.0F, 2.0F, 3.0F, 4.0F, 5.0F)
on("calculating a simple moving average") {
val res = movingAverage(entries = input, window = 3, averageCalc = { mean() })
it("should give the correct result") {
assertThat(res).containsExactly(1.0F, 1.5F, 2.0F, 3.0F, 4.0F)
.inOrder()
}
}
on("calculating a weighted moving average") {
val res = movingAverage(entries = input, window = 3, averageCalc = { weightedMean() })
it("should give the correct result") {
assertThat(res).containsExactly(1.0F, 1.6666666F, 2.3333333F, 3.3333333F, 4.3333335F)
.inOrder()
}
}
}
})
Addendum: Efficiency
The above calculation is not memory efficient. For a list of 1,000 numbers, and a window size of 10, it would create 1,000 lists of 10 numbers, and then calculate the avarages of each list. That’s not too bad for small lists, but could get out of hand for large lists and window sizes.
What can I do about that?
Kotlin Sequences provide a way to lazily evaluate collections. New in Kotlin 1.2 is Sequence<T>.windowed(…) which will lazily provide a sequence of sliding windows that I can then use in my calculations.
Leave a comment