一尘不染

扩展通用数组 采用协议

swift

假设我已经定义了这样的协议:

protocol EuclideanPoint {
    func distance(other: Self) -> Double
    func dimension() -> UInt
}

现在,我想扩展[Float][Double]采用该协议。

但是下面的代码:

extension [Float]: EuclideanPoint {
    func distance(other: [Float]) {
        return Double(zip(self, other).map{a, b in pow(a-b,2)}.reduce(0, combine: +))
    }
    func dimension() {
        return UInt(self.count)
    }
}

由于错误而无效

错误:必须在非专用泛型类型“ Array”上声明受约束的扩展,并使用“ where”子句指定约束

我发现类似的问题,但建议的解决方案是使用extension CollectionType where Generator.Element == S { ... },但在这种情况下它会导致错误:

错误:协议“ CollectionType”只能用作一般约束,因为它具有“自我”或相关类型要求

有什么解决办法吗?

编辑:

使用建议的解决方案:

protocol DoubleConvertibleType {
    var doubleValue: Double { get }
}

extension Double : DoubleConvertibleType { var doubleValue: Double { return self         } }
extension Float  : DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension CGFloat: DoubleConvertibleType { var doubleValue: Double { return Double(self) } }

extension Array where Element : DoubleConvertibleType {
    func distance(other: Array) -> Double {
        return Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue, 2) }.reduce(0, combine: +))
    }

    func dimension() -> UInt {
        return UInt(self.count)
    }
}

给出[Double][Float].distance().dimension()方法。但是[Double][Float]不能代替符合EuclideanPoint协议所需的内容,从而产生错误:

错误:类型“ [Double]”不符合协议“ EuclideanPoint”


阅读 224

收藏
2020-07-07

共1个答案

一尘不染

已编辑


以下解决方案在某种程度上是通用的,符合protocol EuclidianPoint,并且基于两个假设:

  • 我们可以distanceEuclideanPoint协议中为方法的蓝图包括通用类型约束,并且Self我们将使用通用([T])代替参数类型是。我们将然而,探明(在编译时间)[T]是相同类型的Self(和这里,Self[Double][Float][Int]类型),并 查明[T]符合协议EuclidianPoint

  • 那你真行,我们离开函数式编程技术,如.map.reduce出这个特定的应用程序,而只注重敷脸 “采用欧几里德协议通用阵列” 。这些.map.reduce等在斯威夫特的壮举确实是整洁和有用的,但在许多应用程序背后的引擎盖for循环只是包装,这样你就不会失去对在手动势在必行的风格做事任何性能。实际上,.reduce由于重复执行array-copy-assignments减少数组的同时,已知执行非可选的操作(我在这里不再赘述…)。无论如何,也许您可​​以利用我的示例并将其调整回更多的功能范例。


我们从一个自定义类型协议开始,该协议MyTypes将充当我们要包含在泛型中的类型的接口。我们还添加了稍微更新的EuiclidianPoint协议,在该协议中,我们将协议用作对功能蓝图中使用MyTypes的泛型的类型约束。T``distance (...)

/* Used as type constraint for Generator.Element */
protocol MyTypes {
    func -(lhs: Self, rhs: Self) -> Self
    func +=(inout lhs: Self, rhs: Self)
}

extension Int : MyTypes { }
extension Double : MyTypes { }
extension Float : MyTypes { }
    /* Extend with the types you wish to be covered by the generic ... */

/* Used as extension to Array : blueprints for extension method
to Array where Generator.Element are constrainted to MyTypes */
protocol EuclideanPoint {
    func distance<T: MyTypes> (other: [T]) -> Double?
    func dimension() -> UInt
}

请注意,我已将Double返回值distance更改为可选值;您可以按照自己的意愿进行处理,但是如果selfand
other数组的长度不同,或者类型Self和类型[T]不同,则需要显示不符合项-我将nil在此使用。

现在,我们可以Array通过EuclidianPoint协议来实现我们的扩展:

/* Array extension by EuclideanPoint protocol */
extension Array : EuclideanPoint {

    func distance<T: MyTypes> (other: [T]) -> Double? {
        /* [T] is Self? proceed, otherwise return nil */
        if let a = self.first {
            if a is T && self.count == other.count {
                var mySum: Double = 0.0
                for (i, sElement) in self.enumerate() {
                    mySum += pow(((sElement as! T) - other[i]) as! Double, 2)
                }
                return sqrt(mySum)
            }
        }
        return nil
    }

    func dimension() -> UInt {
        return UInt(self.count)
    }
}

请注意,在distance函数的内部if子句中,我们使用了显式的down强制转换为T,但是由于我们断言的元素Self是type
T,所以可以。

无论如何,我们已经完成了,我们可以测试我们的“通用”数组扩展,我们现在注意到这些扩展也符合您的协议EuclidianPoint

/* Tests and Examples */
let arr1d : [Double] = [3.0, 4.0, 0.0]
let arr2d : [Double] = [-3.0, -4.0, 0.0]
let arr3d : [Double] = [-3.0, -4.0]

let arr1f : [Float] = [-3.0, -4.0, 0.0]

let arr1i = [1, 2, 3]

let _a = arr1d.dimension() // 3, OK
let _b = arr1d.distance(arr2d) // 10, OK (A->B dist)
let _c = arr1d.distance(arr1f) // nil (Incomp. types)
let _d = arr1d.distance(arr3d) // nil (Incomp. sizes)
let _e = arr1i.distance(arr1d) // nil (Incomp. types)

    /* for use in function calls: generic array parameters constrained to
       those that conform to protocol 'EuclidianPoint', as requested     */
func bar<T: MyTypes, U: protocol<EuclideanPoint, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Double? {

    // ...

    return arr1.distance(Array(arr2))
        /* We'll need to explicitly tell the distance function
           here that we're sending an array, by initializing an 
           array using the Array(..) initializer                */
}
let myDist = bar(arr1d, arr2d) // 10, OK

好!


我的第一个答案中还剩下一条笔记:

实际上刚刚在这里要求将泛型类型Array扩展为协议:

  • 通过遵循Swift 2中的协议来扩展类型数组

共识是您 不能以 您期望的“简洁快速”的方式将 数组通用扩展到协议
。但是,有一些变通办法可以模仿这种行为,其中一个就是我上面使用的变通办法。如果您对另一种方法感兴趣,建议您研究此线程。

2020-07-07