3

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.

tylyo
  • 572
  • 5
  • 16
  • 1
    Please [check this](http://stackoverflow.com/questions/28288148/making-my-function-calculate-average-of-array) – Zumry Mohamed Nov 21 '15 at 10:40
  • thanks, I will propose this logic with that approach. it should be better – tylyo Nov 21 '15 at 10:43
  • @Leo: This is not a duplicate of http://stackoverflow.com/questions/28288148/making-my-function-calculate-average-of-array. This is not a simple average calculation, and OP's problem was that the iteration step involves the current element index. I cannot see how that is addressed in the referenced thread. – Martin R Nov 21 '15 at 13:14

1 Answers1

3

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) }
    }
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • the point is also that enumerate() will add a loop more on the array (I'm looking how enumerate() works right now) and this slow down performance if the array has a big numbers of elements – tylyo Nov 21 '15 at 11:21
  • @tylyo: Both your original code and my suggested extension method (using enumerate) compute the incremental average of an array with 1,000,000 random numbers in about 0.008 seconds on my computer. There is no significant difference in the performance. – Martin R Nov 21 '15 at 11:59
  • is `Element(0)` meant to be something generic so that if it was `Int` it would do `Int(0)`...if `Double` it would `Double(0)` and initialize to its type? – mfaani Dec 09 '16 at 01:27
  • @Honey: `Element(0)` calls the `init(_ value : Int)` method which is required in the `NumericType` protocol. Add floating point types have such a `init(_ value : Int)` method. – But the custom protocol is no longer necessary with Swift 3, I have updated the answer accordingly. – Martin R Dec 09 '16 at 06:31
  • @MartinR @Honey: *add floating point types have such a `init(_ value : Int)` method.* what? Element(0) calls `init ( _ value : Int) while Element(0.0) calls `init (_ value Float)` – mfaani Dec 09 '16 at 11:22
  • @Honey: No, `$1.0` is an integer as well: The offset/index from the enumerated() method. – Martin R Dec 09 '16 at 11:50
  • You mean it's a tuple?! – mfaani Dec 10 '16 at 10:21
  • 1
    @Honey: `enumerated()` returns a sequence of `(index, element)` tuples. – Martin R Dec 10 '16 at 15:44