2

fellow exchangers. Recently, I was familiarizing myself with swift by solving some trivial exercises and faced one where statistics should be calculated. The goal was to calculate the mean (sum of all elements divided by amount) for an array of numbers. I decided to follow the best known way to solve this, and defined an extension over a collection with a function that returns a numeric value:

//this is specific case with Integers. Seems bold enought
extension Array where Element == Int {
    func mean() -> Double {
        var result = 0.0
        for i in self {
            result += Double(i)
        }
        return result / Double(self.count)
    }
}

[1,2,3,4,5,6,7,8,9].mean() //outputs 5.0

The code above runs without a problem. But I want to be able to apply mean() to any sequence of numerals (Floats, Doubles, etc), so the try is:

extension Collection where Element: Numeric {
    func mean() -> some Numeric {
        var result = Element.init(exactly: 0)!
        for i in self {
            result += i
        }
        return result / Element.init(exactly: self.count)! 
    }
}

This would allow to get a result from any numeral array:

var array1: [Float] = ...
var array2: [UInt] = ... 
var array3: [Int8] = ... //nutty case

But the compilation fails with a message that says that, because values in that collection can be of a different concrete type, it is not possible to invoke a division operator due to swift strict typing. So, the only option to implement needed functionality is to extend all concrete types that operate with numbers (Int, UInt, UInt8, ...), which is a robust thing to do (there are ~20 of them). Given swift's great abstractization possibilities with extensions, is it possible to:

extension Collection where Adopter: HomogeneousCollection { ... }

Also, some types cannot be big numbers: Int8 can hold up to 256 numbers, so a collection of [Int8] can only give a correct result if there is less than 128 elements, otherwise division by nil would occur.

So I ask, what is the most appropriate way to declare a function to calculate a mean for a homogeneous sequential collection? Also, how would you solve for a case when collection is empty?

oneWayTicket
  • 101
  • 6
  • Possible duplicate of https://stackoverflow.com/questions/54391361/extension-for-average-to-return-double-from-numeric-generic. See also https://stackoverflow.com/questions/53771879/is-there-a-way-to-convert-any-generic-numeric-into-a-double. – Martin R Sep 26 '19 at 14:39

1 Answers1

0

I found a suitable solution.

extension Collection where Element: Numeric {
    /// Returns the total sum of all elements in the array
    var total: Element { return reduce(0, +) }
}

extension Collection where Element: BinaryInteger {
    /// Returns the average of all elements in the array
    var average: Double? {
        return isEmpty ? nil : Double(total) / Double(count)
    }
}

extension Collection where Element: BinaryFloatingPoint {
    /// Returns the average of all elements in the array
    var average: Element? {
        return isEmpty ? nil : total / Element(count)
    }
}

(1...9).average //Range<Int> -> 5.0

[1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9].average //[Double] -> 5.5

Although I wanted a result to be of the same type as a collection it belongs to[Type] -> Type, in reality that was not always necessary and Double is enough for me [Type] -> Double. It also turns out that swift enforces a collection to be homogeneous in this case for some reason. I was concerned at first that code above wouldn't work since a collection(array) is initialized to hold an opaque type, but compilation reports a massage: 'BinaryInteger' can only be used as a generic constraint, so concrete types are preserved here.

//this compiles
protocol SomeProtocol {}
struct SomeStruct: SomeProtocol {}
struct AnotherStruct: SomeProtocol {}

var myObject: [SomeProtocol] = [SomeStruct(), AnotherStruct()]

//but this doesnt, though all elements conform to BinaryInteger
let arr: [BinaryInteger] = [0 as Int, 1 as Int8, 2 as Int16]
oneWayTicket
  • 101
  • 6