web-dev-qa-db-fra.com

Comment utiliser Swift flatMap pour filtrer les options d'un tableau

Je suis un peu confus par flatMap (ajouté à Swift 1.2)

Disons que j'ai un tableau de type facultatif, par exemple. 

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]

Dans Swift 1.1, je ferais un filtre suivi d'une carte comme celle-ci:

let filtermap = possibles.filter({ return $0 != nil }).map({ return $0! })
// filtermap = [1, 2, 3, 4, 5]

J'ai essayé de le faire en utilisant flatMap de plusieurs manières:

var flatmap1 = possibles.flatMap({
    return $0 == nil ? [] : [$0!]
})

et 

var flatmap2:[Int] = possibles.flatMap({
    if let exercise = $0 { return [exercise] }
    return []
})

Je préfère la dernière approche (car je n'ai pas à décompresser de force $0!... je suis terrifié pour cela et à les éviter à tout prix) sauf que je dois spécifier le type Array.

Existe-t-il une alternative qui détermine le type en fonction du contexte, mais qui n’a pas de décompression forcée?

21
MathewS

J'aime toujours la première solution, qui crée un seul tableau intermédiaire Il peut être légèrement plus compact être écrit comme

let filtermap = possibles.filter({ $0 != nil }).map({ $0! })

Mais flatMap() sans annotation de type et sans décompression forcée .__ est possible:

var flatmap3 = possibles.flatMap {
    flatMap($0, { [$0] }) ?? []
}

flatMap extérieur est la méthode tableau

func flatMap<U>(transform: @noescape (T) -> [U]) -> [U]

et la flatMap intérieure est la fonction

func flatMap<T, U>(x: T?, f: @noescape (T) -> U?) -> U?

Voici une comparaison simple des performances (compilée en mode Release) . Elle montre que la première méthode est plus rapide, d’un facteur .__ environ de 10:

let count = 1000000
let possibles : [Int?] = map(0 ..< count) { $0 % 2 == 0 ? $0 : nil }

let s1 = NSDate()
let result1 = possibles.filter({ $0 != nil }).map({ $0! })
let e1 = NSDate()
println(e1.timeIntervalSinceDate(s1))
// 0.0169369578361511

let s2 = NSDate()
var result2 = possibles.flatMap {
    flatMap($0, { [$0] }) ?? []
}
let e2 = NSDate()
println(e2.timeIntervalSinceDate(s2))
// 0.117663979530334
15
Martin R

Relatif à la question. Si vous appliquez flatMap à un tableau facultatif, n'oubliez pas de forcer ou de forcer le désenroulement de votre tableau, sinon il appellera flatMap sur Optional et non des objets conformes au protocole Sequence. J'ai fait cette erreur une fois, par exemple. lorsque vous souhaitez supprimer des chaînes vides:

var texts: [String]? = ["one", "two", "", "three"] // has unwanted empty string

let notFlatMapped = texts.flatMap({ $0.count > 0 ? $0 : nil })
// ["one", "two", "", "three"], not what we want - calls flatMap on Optional

let flatMapped = texts?.flatMap({ $0.count > 0 ? $0 : nil })
// ["one", "two", "three"], that's what we want, calls flatMap on Array
0
Au Ris

Vous pouvez utiliser reduce:

let flattened = possibles.reduce([Int]()) { 
        if let x = $1 { return $0 + [x] } else { return $0 } 
    }

Vous déclarez toujours le type, mais il est légèrement moins gênant.

0
letvargo

Comme c'est quelque chose qui me semble faire beaucoup, j'explore une fonction générique pour le faire.

J'ai essayé d'ajouter une extension à Array pour pouvoir faire quelque chose comme possibles.unwraped, mais je ne savais pas comment créer une extension sur un tableau. Au lieu de cela utilisé un opérateur personnalisé - la partie la plus difficile ici était de savoir quel opérateur choisir. En fin de compte, j'ai choisi >! pour montrer que le tableau est filtré > puis non enveloppé !.

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]

postfix operator >! {}

postfix func >! <T>(array: Array<T?>) -> Array<T> {
    return array.filter({ $0 != nil }).map({ $0! })
}

possibles>!
// [1, 2, 3, 4, 5]
0
MathewS