如何在Swift中扩展类型化数组?

时间:2014-06-04 00:26:14

标签: arrays swift

如何使用自定义功能工具扩展Swift的Array<T>T[]类型?

浏览Swift的API文档显示,Array方法是T[]的扩展,例如:

extension T[] : ArrayType {
    //...
    init()

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

复制并粘贴相同的来源并尝试任何变体时:

extension T[] : ArrayType {
    func foo(){}
}

extension T[] {
    func foo(){}
}

无法使用错误构建:

  

名义类型T[]无法延期

使用完整类型定义失败,Use of undefined type 'T',即:

extension Array<T> {
    func foo(){}
}

它也因Array<T : Any>Array<String>而失败。

奇怪的是,Swift允许我使用:

扩展无类型数组
extension Array {
    func each(fn: (Any) -> ()) {
        for i in self {
            fn(i)
        }
    }
}

它让我打电话给:

[1,2,3].each(println)

但是我无法创建一个正确的泛型类型扩展,因为当它流经该方法时类型似乎丢失了,例如尝试replace Swift's built-in filter with

extension Array {
    func find<T>(fn: (T) -> Bool) -> T[] {
        var to = T[]()
        for x in self {
            let t = x as T
            if fn(t) {
                to += t
            }
        }
        return to
    }
}

但编译器将其视为无类型,但仍允许使用以下命令调用扩展名:

["A","B","C"].find { $0 > "A" }

当使用调试器逐步执行时指示类型为Swift.String但是构建错误是尝试访问它而不是首先将其转换为String,即:

["A","B","C"].find { ($0 as String).compare("A") > 0 }

是否有人知道创建类似于内置扩展程序的类型化扩展方法的正确方法是什么?

10 个答案:

答案 0 :(得分:241)

对于使用扩展类型化数组,下面适用于我(Swift 2.2 )。例如,对类型化数组进行排序:

class HighScoreEntry {
    let score:Int
}

extension Array where Element:HighScoreEntry {
    func sort() -> [HighScoreEntry] {
      return sort { $0.score < $1.score }
    }
}

尝试使用 struct typealias 执行此操作会出错:

Type 'Element' constrained to a non-protocol type 'HighScoreEntry'

更新

要使用非类扩展类型化数组,请使用以下方法:

typealias HighScoreEntry = (Int)

extension SequenceType where Generator.Element == HighScoreEntry {
    func sort() -> [HighScoreEntry] {
      return sort { $0 < $1 }
    }
}

Swift 3 中,某些类型已重命名:

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}

答案 1 :(得分:62)

经过一段时间尝试不同的事情后,解决方案似乎从签名中移除了<T>,如:

extension Array {
    func find(fn: (T) -> Bool) -> [T] {
        var to = [T]()
        for x in self {
            let t = x as T;
            if fn(t) {
                to += t
            }
        }
        return to
    }
}

现在可以正常工作而不会出现构建错误:

["A","B","C"].find { $0.compare("A") > 0 }

答案 2 :(得分:8)

我遇到了类似的问题 - 希望使用swap()方法扩展通用数组,该方法应该采用与数组相同类型的参数。但是如何指定泛型类型?我通过反复试验发现以下情况有效:

extension Array {
    mutating func swap(x:[Element]) {
        self.removeAll()
        self.appendContentsOf(x)
    }
}

关键是&#39;元素&#39;。请注意,我没有在任何地方定义此类型,它似乎自动存在于数组扩展的上下文中,并且指的是数组元素的类型。

我不是100%肯定那里发生了什么,但我认为这可能是因为&#39;元素&#39;是数组的关联类型(请参阅&#39;关联类型&#39;此处https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189

但是,我在数组结构参考(https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift/struct/s:Sa)中看不到任何这样的引用......所以我还是有点不确定。

答案 3 :(得分:5)

使用Swift 2.2 : 尝试从字符串数组中删除重复项时,我遇到了类似的问题。我能够在Array类上添加一个扩展,它正是我想做的事情。

extension Array where Element: Hashable {
    /**
     * Remove duplicate elements from an array
     *
     * - returns: A new array without duplicates
     */
    func removeDuplicates() -> [Element] {
        var result: [Element] = []
        for value in self {
            if !result.contains(value) {
                result.append(value)
            }
        }
        return result
    }

    /**
     * Remove duplicate elements from an array
     */
    mutating func removeDuplicatesInPlace() {
        var result: [Element] = []
        for value in self {
            if !result.contains(value) {
                result.append(value)
            }
        }
        self = result
    }
}

将这两个方法添加到Array类允许我调用数组中的两个方法之一并成功删除重复项。请注意,数组中的元素必须符合Hashable协议。现在我可以这样做:

 var dupes = ["one", "two", "two", "three"]
 let deDuped = dupes.removeDuplicates()
 dupes.removeDuplicatesInPlace()
 // result: ["one", "two", "three"]

答案 4 :(得分:4)

如果您想了解如何在此github repo https://github.com/ankurp/Cent

中学习扩展数组和其他类型的构建类的结帐代码

从Xcode 6.1开始,扩展数组的语法如下

extension Array {
    func at(indexes: Int...) -> [Element] {
        ... // You code goes herer
    }
}

答案 5 :(得分:3)

I had a look at the Swift 2 standard library headers, and here is the prototype for the filter function, which makes it quite obvious how to roll your own.

extension CollectionType {
    func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]
}

It's not an extension to Array, but to CollectionType, so the same method applies to other collection types. @noescape means that the block passed in will not leave the scope of the filter function, which enables some optimisations. Self with a capital S is the class we are extending. Self.Generator is an iterator that iterates through the objects in the collection and Self.Generator.Element is the type of the objects, for example for an array [Int?] Self.Generator.Element would be Int?.

All in all this filter method can be applied to any CollectionType, it needs a filter block which takes an element of the collection and returns a Bool, and it returns an array of the original type. So putting this together, here's a method that I find useful: It combines map and filter, by taking a block that maps a collection element to an optional value, and returns an array of those optional values that are not nil.

extension CollectionType {

    func mapfilter<T>(@noescape transform: (Self.Generator.Element) -> T?) -> [T] {
        var result: [T] = []
        for x in self {
            if let t = transform (x) {
                result.append (t)
            }
        }
        return result
    }
}

答案 6 :(得分:2)

import Foundation

extension Array {
    var randomItem: Element? {
        let idx = Int(arc4random_uniform(UInt32(self.count)))
        return self.isEmpty ? nil : self[idx]
    }
}

答案 7 :(得分:2)

扩展所有类型:

extension Array where Element: Comparable {
    // ...
}

扩展一些类型:

extension Array where Element: Comparable & Hashable {
    // ...
}

扩展特殊类型:

extension Array where Element == Int {
    // ...
}

答案 8 :(得分:0)

Swift 2.x

您还可以扩展数组以符合包含泛型类型方法的blue-rpints的协议,例如,包含符合某些类型约束的所有通用数组元素的自定义函数工具的协议,例如协议MyTypes 。使用这种方法的好处是,您可以编写采用泛型数组参数的函数,约束条件是这些数组参数必须符合您的自定义函数实用程序协议,比如协议MyFunctionalUtils

您可以通过将数组元素约束为MyTypes的类型隐式地获取此行为,或者 - 我将在下面描述的方法中显示 - ,非常整洁,明确地,让您的通用数组函数头直接显示输入数组符合MyFunctionalUtils

我们从Protocols MyTypes开始用作类型约束;通过此协议扩展您希望适合您的泛型的类型(下面的示例扩展了基本类型IntDouble以及自定义类型MyCustomType

/* Used as type constraint for Generator.Element */
protocol MyTypes {
    var intValue: Int { get }
    init(_ value: Int)
    func *(lhs: Self, rhs: Self) -> Self
    func +=(inout lhs: Self, rhs: Self)
}

extension Int : MyTypes { var intValue: Int { return self } }
extension Double : MyTypes { var intValue: Int { return Int(self) } }
    // ...

/* Custom type conforming to MyTypes type constraint */
struct MyCustomType : MyTypes {
    var myInt : Int? = 0
    var intValue: Int {
        return myInt ?? 0
    }

    init(_ value: Int) {
        myInt = value
    }
}

func *(lhs: MyCustomType, rhs: MyCustomType) -> MyCustomType {
    return MyCustomType(lhs.intValue * rhs.intValue)
}

func +=(inout lhs: MyCustomType, rhs: MyCustomType) {
    lhs.myInt = (lhs.myInt ?? 0) + (rhs.myInt ?? 0)
}

协议MyFunctionalUtils(保存蓝图我们的附加通用数组函数实用程序),然后是MyFunctionalUtils的数组扩展;蓝色印刷方法的实施:

/* Protocol holding our function utilities, to be used as extension 
   o Array: blueprints for utility methods where Generator.Element 
   is constrained to MyTypes */
protocol MyFunctionalUtils {
    func foo<T: MyTypes>(a: [T]) -> Int?
        // ...
}

/* Extend array by protocol MyFunctionalUtils and implement blue-prints 
   therein for conformance */
extension Array : MyFunctionalUtils {
    func foo<T: MyTypes>(a: [T]) -> Int? {
        /* [T] is Self? proceed, otherwise return nil */
        if let b = self.first {
            if b is T && self.count == a.count {
                var myMultSum: T = T(0)

                for (i, sElem) in self.enumerate() {
                    myMultSum += (sElem as! T) * a[i]
                }
                return myMultSum.intValue
            }
        }
        return nil
    }
}

最后,测试和两个示例显示了采用泛型数组的函数,分别包含以下情况

  1. 显示隐式断言,即数组参数符合协议&#39; MyFunctionalUtils&#39;,通过类型将数组元素约束为&#39; MyTypes&#39; (函数bar1)。

  2. 显示显式数组参数符合协议&#39; MyFunctionalUtils&#39; (函数bar2)。

  3. 测试和示例如下:

    /* Tests & examples */
    let arr1d : [Double] = [1.0, 2.0, 3.0]
    let arr2d : [Double] = [-3.0, -2.0, 1.0]
    
    let arr1my : [MyCustomType] = [MyCustomType(1), MyCustomType(2), MyCustomType(3)]
    let arr2my : [MyCustomType] = [MyCustomType(-3), MyCustomType(-2), MyCustomType(1)]
    
        /* constrain array elements to MyTypes, hence _implicitly_ constraining
           array parameters to protocol MyFunctionalUtils. However, this
           conformance is not apparent just by looking at the function signature... */
    func bar1<U: MyTypes> (arr1: [U], _ arr2: [U]) -> Int? {
        return arr1.foo(arr2)
    }
    let myInt1d = bar1(arr1d, arr2d) // -4, OK
    let myInt1my = bar1(arr1my, arr2my) // -4, OK
    
        /* constrain the array itself to protocol MyFunctionalUtils; here, we
           see directly in the function signature that conformance to
           MyFunctionalUtils is given for valid array parameters */
    func bar2<T: MyTypes, U: protocol<MyFunctionalUtils, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Int? {
    
        // OK, type U behaves as array type with elements T (=MyTypes)
        var a = arr1
        var b = arr2
        a.append(T(2)) // add 2*7 to multsum
        b.append(T(7))
    
        return a.foo(Array(b))
            /* Ok! */
    }
    let myInt2d = bar2(arr1d, arr2d) // 10, OK
    let myInt2my = bar2(arr1my, arr2my) // 10, OK
    

答案 9 :(得分:-1)

import Foundation

extension Array {

    func calculateMean() -> Double {
        // is this an array of Doubles?
        if self.first is Double {
            // cast from "generic" array to typed array of Doubles
            let doubleArray = self.map { $0 as! Double }

            // use Swift "reduce" function to add all values together
            let total = doubleArray.reduce(0.0, combine: {$0 + $1})

            let meanAvg = total / Double(self.count)
            return meanAvg

        } else {
            return Double.NaN
        }
    }

    func calculateMedian() -> Double {
        // is this an array of Doubles?
        if self.first is Double {
            // cast from "generic" array to typed array of Doubles
            var doubleArray = self.map { $0 as! Double }

            // sort the array
            doubleArray.sort( {$0 < $1} )

            var medianAvg : Double
            if doubleArray.count % 2 == 0 {
                // if even number of elements - then mean average the middle two elements
                var halfway = doubleArray.count / 2
                medianAvg = (doubleArray[halfway] + doubleArray[halfway - 1]) / 2

            } else {
                // odd number of elements - then just use the middle element
                medianAvg = doubleArray[doubleArray.count  / 2 ]
            }
            return medianAvg
        } else {
            return Double.NaN
        }

    }

}