Wie kann ich typisierte Arrays in Swift erweitern?


203

Wie kann ich Swifts erweitern Array<T>oder T[]mit benutzerdefinierten Funktionsprogrammen tippen?

Das Durchsuchen der API-Dokumente von Swift zeigt, dass Array-Methoden eine Erweiterung von T[]z.

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Beim Kopieren und Einfügen derselben Quelle und beim Ausprobieren von Variationen wie:

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

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

Es kann nicht mit dem Fehler erstellt werden:

Der Nenntyp T[]kann nicht erweitert werden

Die Verwendung der vollständigen Typdefinition schlägt fehl mit Use of undefined type 'T', dh:

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

Und es scheitert auch mit Array<T : Any>und Array<String>.

Mit Curiously Swift kann ich ein untypisiertes Array erweitern mit:

extension Array {
    func each(fn: (Any) -> ()) {
        for i in self {
            fn(i)
        }
    }
}

Womit ich anrufen kann:

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

Ich kann jedoch keine richtige generische Typerweiterung erstellen, da der Typ verloren zu gehen scheint, wenn er durch die Methode fließt, z. B. wenn versucht wird, den integrierten Filter von Swift durch Folgendes zu ersetzen :

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

Der Compiler behandelt es jedoch als untypisiert, wobei der Aufruf der Erweiterung weiterhin möglich ist mit:

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

Und wenn Sie mit einem Debugger durchgehen, wird angezeigt, dass der Typ ist, Swift.Stringaber es ist ein Build-Fehler, wenn Sie versuchen, wie ein String darauf zuzugreifen, ohne ihn Stringzuerst zu übertragen, dh:

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

Weiß jemand, wie man eine typisierte Erweiterungsmethode richtig erstellt, die sich wie die integrierten Erweiterungen verhält?


Abgestimmt, weil ich selbst keine Antwort finden kann. Das gleiche extension T[]Bit wird angezeigt, wenn Sie bei gedrückter Befehlstaste auf den Array-Typ in XCode klicken, aber keine Möglichkeit sehen, ihn ohne Fehler zu implementieren.
Benutzername tbd

@usernametbd FYI gerade gefunden, sieht aus wie die Lösung war zu entfernen <T> aus der Methodensignatur .
Mythos

Antworten:


296

Für die Erweiterung typisierter Arrays um Klassen funktioniert das Folgende für mich (Swift 2.2 ). Beispiel: Sortieren eines typisierten Arrays:

class HighScoreEntry {
    let score:Int
}

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

Der Versuch, dies mit einer Struktur oder Typealias zu tun , führt zu einem Fehler:

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

Update :

So erweitern Sie typisierten Arrays mit nicht-Klassen verwenden Sie die folgende Vorgehensweise:

typealias HighScoreEntry = (Int)

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

In Swift 3 wurden einige Typen umbenannt:

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

1
Der Compiler berichtet, dass 'SequenceType' in 'Sequence' umbenannt wurde
Sandover

Warum haben Sie Iterator.Element nicht als Rückgabetyp verwendet [Iterator.Element]?
Gaussblurinc

1
Hallo, können Sie die Funktion für bedingte Konformität in 4.1 erläutern? Was ist neu in 4.1? Könnten wir das in 2.2 machen? Was vermisse ich
osrl

Seit Swift 3.1 können Sie Arrays mit Nicht-Klassen mit der folgenden Syntax erweitern: extension Array wobei Element == Int
Giles

63

Nach einer Weile, in der verschiedene Dinge ausprobiert wurden, scheint die Lösung Folgendes <T>aus der Signatur zu entfernen :

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

Was jetzt wie vorgesehen ohne Buildfehler funktioniert:

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

1
Übrigens Was Sie hier definiert haben, entspricht funktional der vorhandenen filterFunktion:let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo


4
Aha. Doppelte Filterung , scheint eher Buggy mir ... Aber es hält immer noch , dass das filterist funktional äquivalent zu Ihrem find, dh das Ergebnis der Funktion ist das gleiche. Wenn Ihr Filterverschluss Nebenwirkungen hat, werden Ihnen die Ergebnisse möglicherweise nicht gefallen.
Palimondo

2
@Palimondo Genau, der Standardfilter hat ein unerwartetes Verhalten, während das obige Suchimplement wie erwartet funktioniert (und warum es existiert). Es ist funktional nicht äquivalent, wenn der Abschluss zweimal ausgeführt wird, wodurch möglicherweise Variablen mit Gültigkeitsbereich mutiert werden können (das war zufällig ein Fehler, auf den ich gestoßen bin, daher die Frage nach seinem Verhalten). Beachten Sie auch die Frage, in der ausdrücklich erwähnt wird, dass Swift eingebaut werden soll filter.
Mythos

4
Wir scheinen über die Definition des Wortes funktional zu streiten . Ublicherweise in funktionalem Programmierparadigma , wo die filter, mapund reduceFunktionen stammen, sind Funktionen für ihre Rückgabewerte ausgeführt. Im Gegensatz dazu ist die eachoben definierte Funktion ein Beispiel für eine Funktion, die wegen ihrer Nebenwirkung ausgeführt wird, da sie nichts zurückgibt. Ich denke, wir können uns darauf einigen, dass die aktuelle Swift-Implementierung nicht ideal ist und die Dokumentation nichts über ihre Laufzeitmerkmale aussagt.
Palimondo

24

Erweitern Sie alle Typen:

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

Erweitern Sie einige Typen:

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

Erweitern Sie einen bestimmten Typ:

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

8

Ich hatte ein ähnliches Problem - wollte das allgemeine Array mit einer swap () -Methode erweitern, die ein Argument des gleichen Typs wie das Array annehmen sollte. Aber wie geben Sie den generischen Typ an? Ich fand durch Versuch und Irrtum heraus, dass das Folgende funktionierte:

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

Der Schlüssel dazu war das Wort "Element". Beachten Sie, dass ich diesen Typ nirgendwo definiert habe, er scheint automatisch im Kontext der Array-Erweiterung zu existieren und verweist auf den Typ der Array-Elemente.

Ich bin nicht 100% sicher, was dort vor sich geht, aber ich denke, das liegt wahrscheinlich daran, dass 'Element' ein zugeordneter Typ des Arrays ist (siehe 'Zugehörige Typen' hier https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

In der Array-Strukturreferenz ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift) kann ich jedoch keine Referenz dazu sehen / struct / s: Sa ) ... also bin ich mir immer noch ein bisschen unsicher.


1
Arrayist ein generischer Typ: Array<Element>(siehe swiftdoc.org/v2.1/type/Array ), Elementist ein Platzhalter für den enthaltenen Typ. Zum Beispiel: var myArray = [Foo]()bedeutet, dass myArraynur Typ enthalten wird Foo. FooIn diesem Fall wird der generische Platzhalter "zugeordnet" Element. Wenn Sie das allgemeine Verhalten von Array (über die Erweiterung) ändern möchten, verwenden Sie den generischen Platzhalter Elementund keinen konkreten Typ (wie Foo).
David James

5

Verwenden von Swift 2.2 : Beim Versuch, Duplikate aus einem Array von Zeichenfolgen zu entfernen, ist ein ähnliches Problem aufgetreten. Ich konnte der Array-Klasse eine Erweiterung hinzufügen, die genau das tut, was ich wollte.

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

Durch Hinzufügen dieser beiden Methoden zur Array-Klasse kann ich eine der beiden Methoden in einem Array aufrufen und Duplikate erfolgreich entfernen. Beachten Sie, dass die Elemente im Array dem Hashable-Protokoll entsprechen müssen. Jetzt kann ich das machen:

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

Dies könnte auch mit erreicht werden let deDuped = Set(dupes), was Sie in einer zerstörungsfreien Methode zurückgeben könnten, die aufgerufen wird toSet, solange Sie mit der
Typänderung einverstanden sind

@alexpyoung Sie würden die Reihenfolge des Arrays durcheinander bringen, wenn Sie Set ()
Danny Wang

5

Wenn Sie mehr über das Erweitern von Arrays und andere Arten von eingebauten Klassen erfahren möchten, überprüfen Sie den Code in diesem Github-Repo unter https://github.com/ankurp/Cent

Ab Xcode 6.1 lautet die Syntax zum Erweitern von Arrays wie folgt

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

1
@ Rob Aktualisierte die URL
Encore PTL

3

Ich habe mir die Swift 2-Standardbibliotheksheader angesehen, und hier ist der Prototyp für die Filterfunktion, der es ziemlich offensichtlich macht, wie Sie Ihre eigenen rollen.

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

Es handelt sich nicht um eine Erweiterung von Array, sondern um CollectionType. Daher gilt dieselbe Methode für andere Sammlungstypen. @noescape bedeutet, dass der übergebene Block den Bereich der Filterfunktion nicht verlässt, was einige Optimierungen ermöglicht. Selbst mit einem Großbuchstaben S ist die Klasse, die wir erweitern. Self.Generator ist ein Iterator, der die Objekte in der Sammlung durchläuft, und Self.Generator.Element ist der Typ der Objekte, z. B. für ein Array [Int?]. Self.Generator.Element wäre Int?.

Alles in allem kann diese Filtermethode auf jeden CollectionType angewendet werden. Sie benötigt einen Filterblock, der ein Element der Collection übernimmt und einen Bool zurückgibt, und ein Array des ursprünglichen Typs. Wenn ich dies zusammenstelle, ist hier eine Methode, die ich nützlich finde: Sie kombiniert Map und Filter, indem sie einen Block verwendet, der ein Auflistungselement einem optionalen Wert zuordnet, und ein Array dieser optionalen Werte zurückgibt, die nicht Null sind.

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 )

Sie können das Array auch so erweitern, dass es einem Protokoll entspricht, das Blue-Rpints für generische Typmethoden enthält, z. B. ein Protokoll, das Ihre benutzerdefinierten funktionalen Dienstprogramme für alle generischen Array-Elemente enthält, die einer Typbeschränkung entsprechen, z. B. Protokoll MyTypes. Der Vorteil dieses Ansatzes besteht darin, dass Sie Funktionen mit generischen Array-Argumenten schreiben können, mit der Einschränkung, dass diese Array-Argumente Ihrem Protokoll für benutzerdefinierte Funktionsdienstprogramme entsprechen müssen, z MyFunctionalUtils. B. Protokoll .

Sie können dieses Verhalten entweder implizit erhalten, indem Sie die Array-Elemente auf "Typ" beschränken MyTypes, oder - wie ich in der unten beschriebenen Methode zeigen werde - ganz klar und explizit, indem Sie den Header Ihrer generischen Array-Funktionen diese Eingabearrays direkt anzeigen lassen entspricht MyFunctionalUtils.


Wir beginnen mit Protokollen MyTypeszur Verwendung als Typeinschränkung. die Typen erweitern Sie dieses Protokoll in Ihrem Generika passen wollen (Beispiel erstreckt sich unter Grundtypen Intund Doublesowie einen benutzerdefinierten Typ 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)
}

Protokoll MyFunctionalUtils(enthält Blaupausen unserer zusätzlichen Dienstprogramme für generische Array-Funktionen) und danach die Erweiterung von Array um MyFunctionalUtils; Implementierung von Blaupausenverfahren:

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

Schließlich Tests und zwei Beispiele, die eine Funktion zeigen, die generische Arrays mit den folgenden Fällen verwendet

  1. Zeigen der impliziten Behauptung, dass die Array-Parameter dem Protokoll 'MyFunctionalUtils' entsprechen, über einen Typ, der die Arrays-Elemente auf 'MyTypes' (Funktion bar1) beschränkt.

  2. Es wird explizit angezeigt, dass die Array-Parameter dem Protokoll 'MyFunctionalUtils' (Funktion bar2) entsprechen.

Der Test und die Beispiele folgen:

/* 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
Diese Downcasts ( $0 as! Double) kämpfen gegen das Typensystem von Swift und vereiteln meiner Meinung nach auch den Zweck der OP-Frage. Auf diese Weise verlieren Sie jegliches Potenzial für Compiler-Optimierungen für die Berechnungen, die Sie tatsächlich durchführen möchten, und Sie verschmutzen den Namespace des Arrays mit bedeutungslosen Funktionen (warum sollten Sie .calculateMedian () in einem Array von UIViews sehen wollen, oder von irgendetwas anderem als Double?). Es gibt einen besseren Weg.
Ephemer

versuchenextension CollectionType where Generator.Element == Double {}
Ephemer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.