简体   繁体   中英

Swift 3: How to check the type of a generic array

I declare a generic array

  fileprivate var array: [T?]

I have a method average(), which will calculate average if 'T' is Int or Float; otherwise returns 0

  public   func average() -> Float {
    var mean = 0
    if T is Int or Float {
     for   index in (0..<array.count-1){
         mean = mean+array[index]
        }
        mean = mean/array.count
     }
     return mean;
  }

Question: How will I check if array is holding Int/Float ( if T is Int or Float , in above code)

This is a tool for protocols. The useful protocols for your problem are FloatingPoint to handle floating point types (like Float ) and Integer to handle signed integer types (like Int ). These have slightly different implementations, so it's best to write each one separately. Doing this will ensure that this method is only available for appropriate types of T (rather than all possible types, and just returning 0 in those cases).

extension MyStruct where T: FloatingPoint {
    func average() -> T {
        let sum = array.flatMap{$0}.reduce(0, +)
        let count = T(array.count)
        return sum.divided(by: count)
    }
}

extension MyStruct where T: Integer {
    func average() -> Float {
        let sum = array.flatMap{$0}.reduce(0, +)
        let count = array.count
        return Float(sum.toIntMax()) / Float(count.toIntMax())
    }
}

EDIT: Following up a bit more on Caleb's comments below, you may be tempted to think it's ok to just convert integers into floats to generate their average. But this is not safe in general without careful consideration of your ranges. For example, consider the average of [Int.min, Int.max] . That's [-9223372036854775808, 9223372036854775807] . The average of that should be -0.5, and that's what's returned by my example above. However, if you convert everything to floats in order to sum it, you'll get 0, because Float cannot express Int.max precisely. I've seen this bite people in live code when they do not remember that for very large floats, x == x+1 .

Float(Int.max) == Float(Int.max - 1) // true

You will need to iterate over your array and use "if let" to unwrap the type of the values in the array. If they are ints handle them one way, if they are floats handle them another way.

//while iterating through your array check your elements to see if they are floats or ints.

if let stringArray = T as? Int {
    // obj is a string array. Do something with stringArray
}
else {
    // obj is not a string array
}

You can use the type method introduced in Swift 3 in the following way:

let type = type(of: array)
print("type: \(type)") // if T is String, you will see Array<Optional<String>> here

Here's a high level description:

... inside a loop which allows indexing
if let ex = array[index] as? Int {
    your code
    continue // go around the loop again, you're all done here
}
if let ex = array[index] as? Float {
    // other code
    continue  // go around the loop again, you're all done here
}
// if you got here it isn't either of them
// code to handle that
... end of inside the loop

I can explain further if that isn't clear enough.

This is probably the simplest way to do it:

var average: Float {
    let total = array.reduce(0.0) { (runningTotal, item) -> Float in

        if let itemAsFloat = item as? Float {
            return runningTotal + itemAsFloat
        }
        else if let itemAsInt = item as? Int {
            return runningTotal + Float(itemAsInt)
        }
        else {
            return runningTotal
        }
    }
    return total / Float(array.count)
}

Obviously if you want it can be a function and depending on how you're wanting to use it you may need to tweak it.

*Note that it's possible to have both Int and Float in an array of T . eg if T is Any .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM