Attilio
Attilio

Reputation: 583

Swift calculate incremental average with reduce and

The propose is to calculate incrementally the average The code below is the best way I found to calculate incremental average, in order to use it for big numbers and or great array

The following is an example give this array doubles

let values = [14.0,12.0, 23.4,37.5,11.46]

var index = 1

let avg = values.reduce(0.0) { return $0 + ( $1 - $0 ) / Double(index++) }

avg will be 19.672. and it works.

Is it correct from your point of view?

Is there a way in order to accomplish this with something like:

let avg = values.averageIncr()

What I don't like is that I have to use and index?

[updated]

a step further, thanks to @Martin R contribute

protocol NumericType:Equatable {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
    init(_ value : Int)
}

extension Double : NumericType { }

extension Array where Element : NumericType {
    mutating func avg_inc_adding( element: Element, var startAvg:Element? = Element(0)  ) throws -> Element{
        if count > 0 && startAvg! == Element(0) {
            startAvg = self.avg_inc()
        }
        append(element)
        return startAvg! + (element - startAvg!) / Element(self.count - 1)
    }
    func avg_inc() -> Element {
        return enumerate().reduce(Element(0)) { $0 + ( $1.1 - $0 ) / Element($1.0 + 1) }
    }
}

in this way I'm be able to do something like:

var average:Double = values.avg_inc()
do {
    average = try values.avg_inc_adding(34.6,startAvg: z)
}catch {

}

that fit with my needs, and I hope with the ones for somebody else.

Upvotes: 3

Views: 952

Answers (1)

Martin R
Martin R

Reputation: 539965

You get the same result without the need for an "external variable" with

let avg = values.enumerate().reduce(0.0) { $0 + ( $1.1 - $0 ) / Double($1.0 + 1) }

because enumerate() returns a sequence of index/element pairs.

Implementing that as an extension method is a bit complicated but possible:

protocol NumericType {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
    init(_ value : Int)
}

extension Double : NumericType { }

extension Array where Element : NumericType {
    func averageIncr() -> Element {
        return enumerate().reduce(Element(0)) { $0 + ( $1.1 - $0 ) / Element($1.0 + 1) }
    }
}

let avg = values.averageIncr()

The Element type in an Array extension can only restricted to a protocol, not a type. Similarly as in e.g. What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift? or How can we create a generic Array Extension that sums Number types in Swift?, you have to define a protocol which contains all the methods needed in your calculation.

The restriction Element: FloatingPointType is not sufficient because the FloatingPointType protocol does not define any arithmetic operations (+, -, /).


Update for Swift 3: As of Swift 3/Xcode 8, floating point types conform to the FloatPoint protocol and that defines the arithmetic operations (+, -, /). Therefore a custom protocol is no longer necessary:

extension Array where Element: FloatingPoint {
    func averageIncr() -> Element {
        return enumerated().reduce(Element(0)) { $0 + ( $1.1 - $0 ) / Element($1.0 + 1) }
    }
}

Upvotes: 3

Related Questions