1

I have a problem with a class using a generic type T for an internal array which can be an Int or Double. The function average shall calcucualte the average values of all Int or Double values in the array.

class MathStatistics<T: Comparable> {
    var numbers = [T]()

    func average() -> Double? {
        if numbers.count == 0 {
            return nil
        }

        var sum:T

        for value in numbers {
           sum = sum + value
        }

        return (sum / numbers.count)
    }
}

Xcode reports an error in line the following lines:

sum = sum + value Binary operator '+' cannot apply two T operants

return (sum / numbers.count) Couldn't find an overload for '/' that accepts the supplied arguments

Morpheus78
  • 870
  • 1
  • 9
  • 21
  • Why are you using Comparable? Why not just cast the integer to a double? – Liam Sorsby Apr 29 '15 at 20:10
  • This should answer a major part of your question: [What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift?](http://stackoverflow.com/questions/25575513/what-protocol-should-be-adopted-by-a-type-for-a-generic-function-to-take-any-num). You'll have to extend the protocol if you want all possible types to be convertible to a Double. – Martin R Apr 29 '15 at 20:22
  • `Comparable` I need for other functions which I excluded here to simplify the example. – Morpheus78 Apr 29 '15 at 20:37
  • @Morpheus78: The *result* should always be a Double, no matter what the element type is? – Martin R Apr 29 '15 at 20:42
  • I was thinking about the following situation with an Int array of [3, 5, 2]. The average values is 3.333. If I use T it will be just 3. Maybe it will be better to implement to functions. One with T and one with Double or Float as return value. – Morpheus78 Apr 29 '15 at 20:49
  • @Morpheus78: You can define a protocol and a method which converts all the numbers to Double and computes the average as a Double (that's why I asked). – Martin R Apr 29 '15 at 20:52
  • @Martin R: Maybe that's the better way, because now I have the problem to convert the generic type `T` to `Double`. Up to know I found no solution for it. – Morpheus78 May 01 '15 at 10:08

3 Answers3

3

For this you need to create a new protocol that lets Swift know any instance of T can have numeric operators performed on it, for example:

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

extension Double  : NumericType {}
extension Int     : NumericType {}

Source: What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift?

Now when you define your MathStatistics class:

class MathStatistics<T: NumericType> {
    var numbers = [T]()

    func average() -> T? {
        if numbers.count == 0 {
            return nil
        }

        let sum = numbers.reduce(T(0)) { $0 + $1 }
        return sum / T(numbers.count)
    }
}

Now you can use MathsStatistics like so:

let stats = MathStatistics<Int>()
stats.numbers = [1, 3, 5]
println(stats.average()) // Prints: Optional(3)
Community
  • 1
  • 1
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
2

If your intention is to compute the average in floating point arithmetic then you have to define a protocol which describes all types which can be converted to Double:

protocol DoubleConvertible {
    var doubleValue : Double { get }
}

extension Double : DoubleConvertible {
    var doubleValue : Double { return self }
}

extension Int : DoubleConvertible {
    var doubleValue : Double { return Double(self) }
}

Then your class would be:

class MathStatistics<T: DoubleConvertible> {
    var numbers = [T]()

    func average() -> Double? {
        if numbers.count == 0 {
            return nil
        }

        var sum = 0.0
        for value in numbers {
            sum = sum + value.doubleValue
        }
        return (sum / numbers.count.doubleValue)
    }
}

Example:

let stats = MathStatistics<Int>()
stats.numbers = [3, 5,2]
println(stats.average()) // Optional(3.33333333333333)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Seems not to work for me. I always receive the error message `T does not have a member named doubleValue` in line `sum = sum + value.doubleValue`. If I understood it correctly `` just says that `T` supports this protocol. Do I have to implement in `MathStatistics` the `var doubleValue...`? – Morpheus78 May 02 '15 at 10:07
  • @Morpheus78: Strange. I have tested that code before posting it and it compiled and worked as expected. – Martin R May 02 '15 at 10:25
  • @Morpheus78: ... Did you add the extension methods for Double and Int? – Martin R May 02 '15 at 10:37
  • Seems to be a bug in Swift. See answer below. – Morpheus78 May 02 '15 at 14:59
  • @Morpheus78: I don't think that is a bug in Swift. The problem is that you way of "merging" the two answers does not work. See ABakerSmith' comment below. – Martin R May 02 '15 at 16:15
0

It seems that there is a bug in SWIFT when I want to use two protocols on a generic type T.

The following code works perfectly.

protocol DoubleConvertible {
    var doubleValue : Double { get }
}

protocol NumericType: Equatable, Comparable, DoubleConvertible {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
    func %(lhs: Self, rhs: Self) -> Self
    init(_ v: Int)
    init(_ v: Double)
}

extension Double : NumericType {
    var doubleValue : Double { return self }
}

extension Int    : NumericType {
    var doubleValue : Double { return Double(self) }
}

class MathStatistics<T: NumericType> {
    var numbers = [T]()

    func average() -> Double? {
        if numbers.count == 0 {
            return nil
        }

        let sum = numbers.reduce(T(0)) { $0 + $1 }
        return sum.doubleValue / numbers.count.doubleValue
    }
}

This version produces the error T does not have a member named doubleValuein line return sum.doubleValue / numbers.count.doubleValue

protocol NumericType: Equatable, Comparable {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
    func %(lhs: Self, rhs: Self) -> Self
    init(_ v: Int)
    init(_ v: Double)
}

protocol DoubleConvertible {
    var doubleValue : Double { get }
}

extension Double : DoubleConvertible {
    var doubleValue : Double { return self }
}

extension Int    : DoubleConvertible {
    var doubleValue : Double { return Double(self) }
}

class MathStatistics<T: NumericType, DoubleConvertible > {
    var numbers = [T]()

    func average() -> Double? {
        if numbers.count == 0 {
            return nil
        }

        let sum = numbers.reduce(T(0)) { $0 + $1 }
        return sum.doubleValue / numbers.count.doubleValue  // error T does not have a member named doubleValue
    }
}
Morpheus78
  • 870
  • 1
  • 9
  • 21
  • 1
    Try replacing `T: NumericType, DoubleConvertible` with `T: protocol` or `T where T: NumericType, T: DoubleConvertible`. – ABakerSmith May 02 '15 at 15:39