首页 文章

扩展通用Array <T>以采用协议

提问于
浏览
3

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

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'子句指定

我发现了类似的问题(如this),但建议的解决方案是使用 extension CollectionType where Generator.Element == S { ... } ,但在此上下文中会导致错误:

错误:协议'CollectionType'只能用作通用约束,因为它具有Self或关联类型要求

这有什么解决方案吗?

编辑:

使用建议的解决方案:

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'

3 回答

  • 1

    EDITED


    以下解决方案有些通用,符合协议 EuclidianPoint ,并基于两个假设:

    • 我们允许在 EuclideanPoint 协议中为方法 distance 的蓝图包含泛型类型约束,并且我们将使用泛型( [T] )而不是参数类型 Self . 但是,我们将确定(在编译时) [T]Self (此处为 [Double][Float][Int] 类型)的类型相同,并确定[T]符合协议 EuclidianPoint .

    • 你可以放弃这个特定应用程序中的函数编程技术,例如 .map.reduce ,并且只关注实现"generic array adopted to euclidian protocol" . Swift中的这些 .map.reduce 等专长确实很整洁有用,但在许多应用程序中只是for-hood-for循环的包装器,所以你不会因为手动命令式的方式而失去任何性能 . 事实上,已知 .reduce 由于重复的数组复制分配而执行非常不可选的,同时减少了数组(我不会在这里更多地介绍......) . 无论如何,也许你可以利用我的例子并将其调整回更具功能性的范例 .


    我们从一个自定义类型协议 MyTypes 开始,它将作为我们想要包含在我们的泛型中的类型的接口 . 我们还添加了略微更新的 EuiclidianPoint 协议,其中我们使用协议 MyTypes 作为 distance (...) 函数蓝图中使用的泛型 T 的类型约束 .

    /* 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
    }
    

    请注意,我已将 Doubledistance 返回更改为可选项;您可以按照自己的意愿处理,但如果 selfother 数组的长度不同,或类型 Self[T] 不同,则需要显示不符合 - 我将在此处使用 nil .

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

    /* 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子句中,我们使用显式向下转换为 T ,但由于我们断言 Self 的元素的类型为 T ,因此可以 .

    无论如何,有了这个,我们已经完成了,我们可以测试我们的"generic"数组扩展,我们现在注意到它也符合您的协议 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扩展到协议实际上最近才在这里被问到:

    一致意见是你无法以"neat swifty"方式对数组进行通用扩展,这是你可能期望的 . 然而,有一些模拟这种行为的解决方法,一种是我上面使用的那种行为 . 如果您对其他方法感兴趣,我建议您查看此主题 .

  • 0

    前言:正如@difri在评论中正确提到的那样,在同时使用通用约束时,我们还无法创建符合协议的扩展 . 已经有几个雷达打开 - 搜索"extension of type with constraints cannot have an inheritance clause"会产生几个雷达 .

    实际答案: Build 在@LeoDabus awesome answer和我的实验我想出了以下内容:

    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)
        }
    }
    

    测试它

    let arr1 = [1.5, 2, 3]
    let arr2 = [5.5, 2, 3]
    let arrD = arr1.distance(arr2)
    

    有点正确打印

    16

    要获得正确的答案(至少我怀疑),你必须将 distance 包装成 sqrt

    return sqrt(Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue,2) }.reduce(0, combine: +)))
    

    然后正确打印

    4

  • 1

    您可以扩展 SequenceType 而不是 Array

    extension SequenceType where Generator.Element == Float {
    //
    }
    

相关问题