web-dev-qa-db-fra.com

Comment puis-je étendre des tableaux typés dans Swift?

Comment puis-je étendre le type Array<T> ou T[] de Swift avec des utilitaires fonctionnels personnalisés?

En parcourant la documentation de l’API de Swift, on constate que les méthodes Array sont une extension du T[], par exemple:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Lorsque vous copiez et collez la même source et essayez des variantes telles que:

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

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

Il ne parvient pas à construire avec l'erreur:

Le type nominal T[] ne peut pas être étendu

L'utilisation de la définition de type complète échoue avec Use of undefined type 'T', c'est-à-dire:

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

Et cela échoue également avec Array<T : Any> et Array<String>.

Curieusement, Swift me permet d'étendre un tableau non typé avec:

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

Ce qui me permet d'appeler avec:

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

Mais je ne peux pas créer une extension de type générique appropriée car le type semble être perdu lorsqu'il passe par la méthode, par exemple en essayant de remplace le filtre intégré de Swift par :

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

Mais le compilateur le traite comme non typé où il permet toujours d'appeler l'extension avec:

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

Et quand il est configuré avec un débogueur, il indique que le type est Swift.String, mais c'est une erreur de construction que d'essayer d'y accéder comme une chaîne sans le convertir en String d'abord, c'est-à-dire:

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

Est-ce que quelqu'un sait quelle est la bonne façon de créer une méthode d'extension typée qui agit comme les extensions intégrées?

185
mythz

Pour étendre les tableaux typés avec classes, le ci-dessous fonctionne pour moi (Swift 2.2). Par exemple, trier un tableau typé:

class HighScoreEntry {
    let score:Int
}

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

Essayer de faire cela avec un struct ou typealias donnera une erreur:

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

Mise à jour:

Pour étendre des tableaux typés avec non-classes, utilisez l’approche suivante:

typealias HighScoreEntry = (Int)

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

Dans Swift certains types ont été renommés:

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}
254
Andrew Schreiber

Après avoir essayé différentes choses, la solution semble supprimer le <T> de la signature, comme ceci:

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

Ce qui fonctionne maintenant comme prévu sans erreurs de construction:

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

J'ai eu un problème similaire - je voulais étendre le tableau général avec une méthode swap (), qui était supposée prendre un argument du même type que le tableau. Mais comment spécifiez-vous le type générique? J'ai trouvé par essais et erreurs que le système ci-dessous fonctionnait:

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

La clé était le mot "élément". Notez que je n'ai défini ce type nulle part, il semble exister automatiquement dans le contexte de l'extension de tableau et je me réfère au type d'éléments de ce tableau.

Je ne suis pas sûr à 100% de ce qui se passe là-bas, mais je pense que c'est probablement parce que 'Element' est un type associé à Array (voir 'Types associés' ici https://developer.Apple.com/library /ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//Apple_ref/doc/uid/TP40014097-CH26-ID189 )

Cependant, je ne vois aucune référence à cela dans la référence à la structure de tableau ( https://developer.Apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//Apple_ref/Swift/struct/s: Sa ) ... alors je suis encore un peu incertain.

8
Daniel Howard

Étendre tous types:

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

Étendre certains types:

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

Étendre un type particulier:

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

Si vous souhaitez en savoir plus sur l’extension des tableaux et d’autres types de code de validation de classes intégrées dans ce dépôt github https://github.com/ankurp/Cent

A partir de Xcode 6.1, la syntaxe pour étendre les tableaux est la suivante

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

sing Swift 2.2: J'ai rencontré un problème similaire lors de la suppression des doublons dans un tableau de chaînes. J'ai pu ajouter une extension à la classe Array qui fait exactement ce que je cherchais à faire.

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'ajout de ces deux méthodes à la classe Array me permet d'appeler l'une des deux méthodes d'un tableau et de supprimer les doublons. Notez que les éléments du tableau doivent être conformes au protocole Hashable. Maintenant je peux faire ça:

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

J’ai jeté un œil aux en-têtes de bibliothèque standard Swift 2, et voici le prototype de la fonction de filtrage, ce qui explique clairement comment vous pouvez rouler le vôtre.

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

Ce n'est pas une extension de Array, mais de CollectionType, donc la même méthode s'applique à d'autres types de collection. @noescape signifie que le bloc transmis ne quittera pas la portée de la fonction de filtrage, ce qui permet certaines optimisations. Le soi avec un S majuscule est la classe que nous étendons. Self.Generator est un itérateur qui parcourt les objets de la collection et Self.Generator.Element est le type des objets, par exemple pour un tableau [Int?] Self.Generator.Element serait Int ?.

Dans l’ensemble, cette méthode de filtrage peut être appliquée à n’importe quel CollectionType. Elle a besoin d’un bloc de filtrage qui prend un élément de la collection et renvoie un Bool, ainsi qu’un tableau du type original. Voici donc une méthode que je trouve utile: elle combine mapper et filtrer, en prenant un bloc qui mappe un élément de la collection sur une valeur facultative et renvoie un tableau de ces valeurs facultatives non nulles.

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
    }
}
3
gnasher729
import Foundation

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

(Swift 2.x)

Vous pouvez également étendre le tableau pour le rendre conforme à un protocole contenant blue-rpints pour les méthodes de type générique, par exemple un protocole contenant vos utilitaires fonctionnels personnalisés pour tous les éléments de tableau génériques conformes à une contrainte de type, par exemple le protocole MyTypes. L'avantage de cette approche est que vous pouvez écrire des fonctions en utilisant des arguments de tableau génériques, avec une contrainte selon laquelle ces arguments de tableau doivent être conformes à votre protocole d'utilitaires de fonction personnalisée, par exemple protocole MyFunctionalUtils.

Vous pouvez obtenir ce comportement soit implicitement, en limitant typiquement les éléments du tableau à MyTypes, soit --- comme je le montrerai dans la méthode que je décris ci-dessous ---, assez proprement, explicitement, en laissant votre en-tête de fonctions de tableau générique indique directement que les tableaux d'entrée sont conformes à MyFunctionalUtils.


Nous commençons par Protocols MyTypes à utiliser comme contrainte de type; étendre les types que vous voulez inclure dans vos génériques avec ce protocole (l'exemple ci-dessous étend les types fondamentaux Int et Double ainsi qu'un type personnalisé 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)
}

Protocole MyFunctionalUtils (contenant les plans de nos utilitaires de fonctions de tableau génériques supplémentaires) et, par la suite, l'extension de Array de MyFunctionalUtils; mise en œuvre de méthode (s) imprimée (s) en bleu:

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

Enfin, des tests et deux exemples montrant une fonction prenant des tableaux génériques, avec les cas suivants, respectivement

  1. Affichage de l'assertion implicite selon laquelle les paramètres du tableau sont conformes au protocole 'MyFunctionalUtils', via le type contraignant les éléments du tableau à 'MyTypes' (fonction bar1) .

  2. Afficher explicitement que les paramètres du tableau sont conformes au protocole 'MyFunctionalUtils' (fonction bar2).

Le test et les exemples sont les suivants:

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