Swiftで型付き配列を拡張するにはどうすればよいですか?


203

カスタム機能ユーティリティを使用して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[]XCodeで配列タイプをコマンドクリックすると同じビットが表示されますが、エラーが発生することなく実装する方法が表示されません。
ユーザー名tbd 2014年

@usernametbd参考<T>までに、メソッドシグネチャから削除することで解決したようです。
mythz 2014年

回答:


296

型付き配列をクラスで拡張するために、以下は私のために機能します(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 }
    }
}

スウィフト3いくつかの種類の名前が変更されました:

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

1
コンパイラーは、「SequenceType」が「Sequence」に名前が変更されたことを報告します
サンドオーバー

戻り値の型でIterator.Elementを使用しなかったのはなぜ[Iterator.Element]ですか?
gaussblurinc 2016年

1
こんにちは、4.1の条件付き適合機能について説明できますか?4.1の新機能 2.2でそれができるでしょうか?何が欠けている
osrl

Swift 3.1以降では、次の構文を使用して、クラス以外の配列を拡張できます
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

5
@Palimondoいいえ、そうではありません。組み込みフィルターはコールバックを2回実行します
mythz 14年

4
そうですか。ダブルフィルタリングは、私にはかなりバギーのようだ...しかし、それはまだと考えているfilterある機能的に同等のあなたへfind、すなわち、関数の結果は同じです。フィルタークロージャーに副作用がある場合、結果は気に入らないかもしれません。
Palimondo 2014年

2
@Palimondoまさに、デフォルトのフィルターは予期しない動作をするのに対し、上記のfind implは期待どおりに機能します(なぜそれが存在するのか)。クロージャーを2回実行する場合、機能的に同等ではありません。これは、スコープ変数を潜在的に変更する可能性があります(たまたま私が遭遇したバグであり、したがって、その動作に関する質問です)。また、質問には、Swiftの組み込みを置き換えたいと具体的に述べていることに注意してくださいfilter
mythz 2014年

4
機能的という言葉の定義について議論しているようです。通常、関数プログラミングのパラダイムではfilter、関数mapreduce関数の起源は、戻り値に対して関数が実行されます。対照的に、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

ただし、配列構造のリファレンス(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]()myArraytypeのみを含むことを意味します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
    }
}

これら2つのメソッドをArrayクラスに追加すると、配列で2つのメソッドの1つを呼び出して、重複を正常に削除できます。配列の要素はHashableプロトコルに準拠する必要があることに注意してください。今私はこれを行うことができます:

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

これはで行うこともできます。これは、型の変更に問題がなければ、let deDuped = Set(dupes)呼び出さtoSetれる非破壊的なメソッドで返すことができます
alexpyoung

@alexpyoung Set()を実行すると、配列の順序が台無しになります
Danny Wang

5

配列の拡張や他のタイプのビルドインクラスについてチェックアウトする場合は、このgithubリポジトリのチェックアウトコードhttps://github.com/ankurp/Cent

Xcode 6.1以降、配列を拡張する構文は次のとおりです

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

1
@RobがURLを更新
Encore PTL

3

私はSwift 2の標準ライブラリヘッダーを確認しました。ここにフィルター関数のプロトタイプがあります。これにより、独自のロールを行う方法が非常に明確になります。

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を返すフィルターブロックを必要とし、元の型の配列を返します。これをまとめると、ここに私が役立つと思うメソッドがあります。これは、コレクション要素をオプションの値にマップするブロックを取り、マップとフィルターを組み合わせて、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
    }
}

2
import Foundation

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

0

Swift 2.x

配列を拡張して、ジェネリック型のメソッドのblue-rpintsを含むプロトコルに準拠するように拡張することもできます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 byの拡張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
    }
}

最後に、一般的な配列を取る関数を示すテストと2つの例、それぞれ次の場合

  1. 示す暗黙「MyTypes」(関数への配列要素を制約タイプを介して、アレイのパラメータはプロトコル「MyFunctionalUtils」に準拠していることを主張しますbar1)。

  2. 表示明示的に配列パラメータが「MyFunctionalUtils」(機能プロトコルに準拠していること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)は、スウィフトの型システムと戦っており、また私の意見では、OPの質問の目的を打ち負かしています。そうすることで、実際に実行したい計算のコンパイラ最適化の可能性が失われ、意味のない関数でArrayの名前空間が汚染されます(UIViewsの配列で.calculateMedian()を表示したいのはなぜですか、またはそのことについてはダブル以外の何か?)もっと良い方法があります。
エフェマ

試してみるextension CollectionType where Generator.Element == Double {}
エフェマ2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.