Come posso estendere gli array digitati in Swift?


203

Come posso estendere Swift Array<T>o T[]digitare con utility funzionali personalizzate?

La navigazione tra i documenti API di Swift mostra che i metodi array sono un'estensione del T[], ad esempio:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Quando copi e incolli la stessa fonte e provi qualsiasi variazione come:

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

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

Non riesce a compilare con l'errore:

Il tipo nominale T[]non può essere esteso

L'uso della definizione di tipo completo non riesce Use of undefined type 'T', ovvero:

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

E fallisce anche con Array<T : Any>e Array<String>.

Curiously Swift mi permette di estendere un array non tipizzato con:

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

Che mi permette di chiamare con:

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

Ma non riesco a creare un'estensione di tipo generico appropriata poiché il tipo sembra essere perso quando scorre attraverso il metodo, ad esempio cercando di sostituire il filtro integrato di Swift con :

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

Ma il compilatore lo considera non tipizzato dove consente ancora di chiamare l'estensione con:

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

E quando si fa un passo avanti con un debugger indica che il tipo è Swift.Stringma è un errore di costruzione provare ad accedervi come una stringa senza lanciarlo per Stringprimo, cioè:

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

Qualcuno sa qual è il modo corretto di creare un metodo di estensione tipizzato che si comporta come le estensioni integrate?


Votato perché non riesco nemmeno a trovare una risposta. Vedere lo stesso extension T[]bit quando si fa clic tenendo premuto il tasto Comando sul tipo di array in XCode, ma non si vede alcun modo per implementarlo senza ottenere un errore.
nome utente tbd

@usernametbd FYI l'ho appena trovato, sembra che la soluzione fosse rimuovere <T>dalla firma del metodo.
Mythz

Risposte:


296

Per estendere le matrici tipizzate con le classi , il seguente funziona per me (Swift 2.2 ). Ad esempio, l'ordinamento di un array digitato:

class HighScoreEntry {
    let score:Int
}

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

Cercare di farlo con una struct o typealias darà un errore:

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

Aggiornamento :

Per estendere le matrici tipizzate con non classi utilizzare il seguente approccio:

typealias HighScoreEntry = (Int)

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

In Swift 3 alcuni tipi sono stati rinominati:

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

1
il compilatore riporta che 'SequenceType' è stato rinominato in 'Sequence'
sandover

Perché non hai usato Iterator.Element nel tipo restituito [Iterator.Element]?
gaussblurinc,

1
ciao, puoi spiegare la funzione di conformità condizionale in 4.1? Cosa c'è di nuovo in 4.1? Potremmo farlo in 2.2? Cosa mi manca
osrl,

Da Swift 3.1 puoi estendere matrici con non classi con la seguente sintassi: extension Array dove Element == Int
Giles

63

Dopo un po 'di provare cose diverse, la soluzione sembra rimuovere <T>la firma come:

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

Che ora funziona come previsto senza errori di compilazione:

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

1
A proposito, ciò che hai definito qui è funzionalmente equivalente alla filterfunzione esistente :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo,


4
Vedo. Il doppio filtraggio mi sembra piuttosto difettoso ... Ma sostiene ancora che filterè funzionalmente equivalente al tuo find, cioè il risultato della funzione è lo stesso. Se la chiusura del filtro ha effetti collaterali, i risultati potrebbero non piacerti, di sicuro.
Palimondo,

2
@Palimondo Esattamente, il filtro di default ha un comportamento inaspettato mentre il precedente find impl funziona come previsto (e perché esiste). Non è funzionalmente equivalente se esegue la chiusura due volte, il che può potenzialmente mutare variabili con ambito (che si è verificato essere un bug in cui mi sono imbattuto, quindi la domanda sul suo comportamento). Si noti inoltre che la domanda menziona specificamente la volontà di sostituire il built-in di Swift filter.
mythz,

4
Sembra che stiamo discutendo della definizione della parola funzionale . Abitualmente, in paradigma di programmazione funzionale dove filter, mape reducefunzioni provengono da, funzioni vengono eseguite per i loro valori di ritorno. Al contrario, la eachfunzione definita in precedenza è un esempio di una funzione eseguita per il suo effetto collaterale, perché non restituisce nulla. Suppongo che possiamo concordare sul fatto che l'attuale implementazione di Swift non sia l'ideale e che la documentazione non indichi nulla delle sue caratteristiche di runtime.
Palimondo,

24

Estendi tutti i tipi:

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

Estendi alcuni tipi:

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

Estendi un tipo particolare :

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

8

Avevo un problema simile: volevo estendere l'array generale con un metodo swap (), che avrebbe dovuto sostenere un argomento dello stesso tipo dell'array. Ma come si specifica il tipo generico? Ho scoperto per tentativi ed errori che il seguito ha funzionato:

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

La chiave era la parola "elemento". Si noti che non ho definito questo tipo da nessuna parte, sembra che esista automaticamente nel contesto dell'estensione della matrice e faccia riferimento a qualunque sia il tipo di elementi della matrice.

Non sono sicuro al 100% di cosa stia succedendo lì, ma penso che probabilmente sia perché 'Element' è un tipo associato di array (vedi 'Tipi associati' qui https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

Tuttavia, non riesco a vedere alcun riferimento di questo nel riferimento alla struttura dell'array ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... quindi non sono ancora sicuro.


1
Arrayè un tipo generico: Array<Element>(vedi swiftdoc.org/v2.1/type/Array ), Elementè un segnaposto per il tipo contenuto. Ad esempio: var myArray = [Foo]()significa che myArrayconterrà solo il tipo Foo. Fooin questo caso viene "mappato" al segnaposto generico Element. Se si desidera modificare il comportamento generale di Array (tramite estensione), si utilizzerà il segnaposto generico Elemente non qualsiasi tipo concreto (come Foo).
David James,

5

Usando Swift 2.2 : ho riscontrato un problema simile nel tentativo di rimuovere i duplicati da una matrice di stringhe. Sono stato in grado di aggiungere un'estensione alla classe Array che fa esattamente quello che stavo cercando di fare.

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

L'aggiunta di questi due metodi alla classe Array mi consente di chiamare uno dei due metodi su un array e rimuovere correttamente i duplicati. Si noti che gli elementi nell'array devono essere conformi al protocollo Hashable. Ora posso fare questo:

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

Questo potrebbe anche essere realizzato let deDuped = Set(dupes), che potresti restituire in un metodo non distruttivo chiamato toSetpurché tu stia bene con il cambio di tipo
alexpyoung

@alexpyoung sbaglierebbe l'ordine dell'array se si imposta Set ()
Danny Wang,

5

Se vuoi sapere come estendere le matrici e altri tipi di codice build in class checkout in questo repository github https://github.com/ankurp/Cent

A partire da Xcode 6.1 la sintassi per estendere le matrici è la seguente

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

1
@Rob Aggiornato l'URL
Encore PTL

3

Ho dato un'occhiata alle intestazioni di libreria standard di Swift 2, ed ecco il prototipo per la funzione di filtro, che rende abbastanza ovvio come creare il tuo.

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

Non è un'estensione di Array, ma CollectionType, quindi lo stesso metodo si applica ad altri tipi di raccolte. @noescape significa che il blocco passato non lascerà l'ambito della funzione filtro, che consente alcune ottimizzazioni. Il Sé con la S maiuscola è la classe che stiamo estendendo. Self.Generator è un iteratore che scorre gli oggetti nella raccolta e Self.Generator.Element è il tipo di oggetti, ad esempio per un array [Int?] Self.Generator.Element sarebbe Int ?.

Tutto sommato questo metodo di filtro può essere applicato a qualsiasi CollectionType, ha bisogno di un blocco filtro che prende un elemento della raccolta e restituisce un valore booleano e restituisce un array del tipo originale. Quindi, unendo questo, ecco un metodo che trovo utile: combina mappa e filtro, prendendo un blocco che mappa un elemento di raccolta su un valore opzionale e restituisce un array di quei valori opzionali che non sono nulli.

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 )

È inoltre possibile estendere l'array per conformarsi a un protocollo contenente blueprint per metodi di tipo generico, ad esempio un protocollo contenente i programmi di utilità personalizzati per tutti gli elementi dell'array generico conformi a un vincolo di tipo, ad esempio protocollo MyTypes. Il vantaggio di questo approccio è che puoi scrivere funzioni prendendo argomenti di array generici, con un vincolo che questi argomenti di array devono essere conformi al tuo protocollo di utilità di funzione personalizzata, ad esempio protocollo MyFunctionalUtils.

È possibile ottenere questo comportamento in modo implicito, limitando gli elementi dell'array a MyTypes, o --- come mostrerò nel metodo che descrivo di seguito ---, abbastanza ordinatamente, esplicitamente, lasciando che l'intestazione delle funzioni dell'array generico mostri direttamente che gli array di input conforme a MyFunctionalUtils.


Iniziamo con i protocolli MyTypesda utilizzare come vincolo di tipo; estendi i tipi che vuoi adattare ai tuoi generici con questo protocollo (l'esempio che segue estende i tipi fondamentali Inte Doubleanche un tipo personalizzato 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)
}

Protocollo MyFunctionalUtils(che contiene i progetti delle nostre utilità aggiuntive per le funzioni di array generici) e, successivamente, l'estensione di Array di MyFunctionalUtils; implementazione di metodi stampati in blu:

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

Infine, test e due esempi che mostrano una funzione che prende array generici, con i seguenti casi, rispettivamente

  1. Mostrando un'asserzione implicita che i parametri dell'array sono conformi al protocollo "MyFunctionalUtils", tramite il tipo che vincola gli elementi degli array a "MyTypes" (funzione bar1).

  2. Mostrando esplicitamente che i parametri dell'array sono conformi al protocollo 'MyFunctionalUtils' (funzione bar2).

Il test e gli esempi seguono:

/* 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
Questi downcast ( $0 as! Double) stanno combattendo contro il sistema di tipi di Swift e, a mio avviso, vanificano anche lo scopo della domanda del PO. In questo modo stai perdendo qualsiasi potenziale di ottimizzazione del compilatore per i calcoli che vuoi effettivamente fare e stai anche inquinando lo spazio dei nomi di Array con funzioni prive di significato (perché dovresti voler vedere .calculateMedian () in una matrice di UIVview, o di tutto tranne che doppio per quella materia?). C'è un modo migliore
effimero,

provaextension CollectionType where Generator.Element == Double {}
effimero
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.