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


203

如何使用自定义功能utils 扩展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)

但是我无法创建适当的泛型类型扩展,因为类型在通过方法时似乎丢失了,例如,尝试将Swift的内置过滤器替换为

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 }

有谁知道创建像内置扩展一样的类型化扩展方法的正确方法是什么?


投票是因为我自己也找不到答案。extension T[]在Command上单击XCode中的Array类型时,看到的是相同的位,但是没有看到任何实现它的方法而没有收到错误。
用户名tbd 2014年

仅供参考,@ usernametbd仅供参考,看来解决方案是<T>从方法签名中删除。
mythz 2014年

Answers:


296

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

class HighScoreEntry {
    let score:Int
}

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

尝试使用structtypealias执行此操作将产生错误:

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
编译报道称,“序列类型”已更名为“序”
sandover

为什么不在返回类型中使用Iterator.Element [Iterator.Element]
gaussblurinc

1
嗨,您能解释一下4.1中的“条件符合性”功能吗?4.1中有什么新功能?我们可以在2.2中做到吗?我缺少什么
osrl

从Swift 3.1开始,您可以使用以下语法扩展具有非类的数组:extension Array where Element == Int
Giles

63

经过一段时间尝试不同的解决方案后,该解决方案似乎<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 }

1
顺便说一句,您在此处定义的功能与现有filter功能等效:let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo 2014年

5
@Palimondo不,不是,内置过滤器执行两次回调
mythz 2014年

4
我懂了。双滤波似乎是相当马车给我...但它仍然认为,filter功能上等同于你的find,也就是函数的结果是一样的。如果您的过滤器关闭有副作用,那么您肯定不喜欢该结果。
Palimondo 2014年

2
@Palimondo确实,默认过滤器具有意外行为,而上面的find impl可以按预期工作(及其存在的原因)。它执行两次关闭操作在功能上并不等效,这可能会导致范围变量发生变化(碰巧是我遇到的错误,因此对其行为提出了疑问)。另请注意,该问题特别提到要替换Swift的内置函数filter
mythz 2014年

4
我们似乎在争论“ 功能性”一词的定义。通常,在函数编程范例中filtermapreduce函数起源于此,函数针对其返回值执行。相比之下,each您在上面定义的函数是一个因副作用而执行的函数的示例,因为它不返回任何内容。我想我们可以同意当前的Swift实现不是理想的,并且文档中没有任何有关其运行时特性的声明。
Palimondo 2014年

24

扩展所有类型:

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

扩展一些类型:

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

扩展特定类型:

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

8

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

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

关键是单词“元素”。请注意,我没有在任何地方定义此类型,它似乎自动存在于数组扩展的上下文中,并且可以引用数组元素的任何类型。

我不是100%知道那里发生了什么,但我认为可能是因为“元素”是数组的关联类型(请参见https://developer.apple.com/library/ios/documentation中的 “关联类型” /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189

但是,我在Array结构参考(https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift中找不到任何参考/ struct / s:Sa)...所以我还是不太确定。


1
Array是通用类型:(Array<Element>请参阅swiftdoc.org/v2.1/type/Array),Element是所包含类型的占位符。例如:var myArray = [Foo]()表示myArray将仅包含type FooFoo在这种情况下,“映射”到通用占位符Element。如果要更改Array的一般行为(通过扩展名),则应使用通用占位符,Element而不要使用任何具体类型(如Foo)。
大卫·詹姆斯

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"]

这也可以通过实现let deDuped = Set(dupes),只要您可以toSet接受类型更改,就可以使用一种称为非破坏性方法的方法返回
alexpyoung

@alexpyoung你会弄乱排列的顺序如果您设置()
丹尼王

5

如果您想学习扩展Arrays和其他类型的内置类,请在此github存储库https://github.com/ankurp/Cent中查看代码

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

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

1
@Rob更新了网址
Encore PTL

3

我看了一下Swift 2标准库头文件,这是filter函数的原型,这使得如何滚动自己很明显。

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

它不是Array的扩展,而是CollectionType的扩展,因此相同的方法适用于其他集合类型。@noescape表示传入的块不会离开过滤器功能的范围,从而可以进行一些优化。具有大写S的自我是我们要扩展的类。Self.Generator是一个迭代器,它迭代集合中的对象,而Self.Generator.Element是对象的类型,例如,数组[Int?] Self.Generator.Element将是Int?。

总而言之,此过滤器方法可以应用于任何CollectionType,它需要一个过滤器块,该块接受集合的元素并返回Bool,并返回原始类型的数组。因此,将它们放在一起,这是我发现有用的一种方法:它通过将一个将collection元素映射到可选值的块并返回一个包含非零的可选值的数组的方法,将map和filter组合在一起。

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


0

Swift 2.x

您还可以扩展数组以使其符合用于泛型类型方法的包含blue-rpint的协议,例如,协议包含针对符合某种类型约束的所有泛型数组元素的自定义功能utils,例如protocol MyTypes。使用这种方法的好处是,您可以编写带有通用数组参数的函数,但要约束这些数组参数必须符合您的自定义函数实用程序协议(例如protocol)MyFunctionalUtils

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


我们从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(持有我们其他泛型数组函数实用程序的蓝图),其后是Array的扩展名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. 示出隐式断言阵列参数符合协议“MyFunctionalUtils”,经由类型约束数组中元素为“MyTypes”(功能bar1)。

  2. 明确显示数组参数符合协议'MyFunctionalUtils'(function bar2)。

测试和示例如下:

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

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

    }

}

2
$0 as! Double在我看来,这些低调()与Swift的类型系统作斗争,并且还破坏了OP提出问题的目的。这样一来,您将失去可能真正需要进行的编译器优化的任何潜力,并且还会使无意义的函数污染Array的名称空间(为什么要在UIViews数组中看到.calculateMedian(),或者除了Double之外,什么都没有?)。有个更好的方法。
星历2015年

尝试extension CollectionType where Generator.Element == Double {}
星历2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.