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

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

Disons que j'ai un tableau d'un type optionnel 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 quelques façons:

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 (parce que je n'ai pas à faire un déballage forcé $0!... Je suis terrifié pour ceux-ci et les éviter à tout prix) sauf que je dois spécifier le tableau type.

Existe-t-il une alternative qui détermine le type par contexte, mais n'a pas le déballage forcé?

21
demandé sur MathewS 2015-04-25 23:27:05

5 réponses

Avec Swift 2 b1, vous pouvez simplement faire

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { $0 }

Pour les versions antérieures, vous pouvez corriger ceci avec l'extension suivante:

extension Array {
    func flatMap<U>(transform: Element -> U?) -> [U] {
        var result = [U]()
        result.reserveCapacity(self.count)
        for item in map(transform) {
            if let item = item {
                result.append(item)
            }
        }
        return result
    }
}

Une mise en garde (ce qui est également vrai pour Swift 2) est que vous devrez peut-être taper explicitement la valeur de retour de la transformation:

let actuals = ["a", "1"].flatMap { str -> Int? in
    if let int = str.toInt() {
        return int
    } else {
        return nil
    }
}
assert(actuals == [1])

Pour plus d'informations, voir http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/

34
répondu Fizker 2015-07-24 17:14:55

J'aime toujours la première solution, qui ne crée qu'un intermédiaire tableau. Il peut être écrit un peu plus compact comme

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

, Mais flatMap(), sans annotation de type et sans forcé le déballage est possible:

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

L'extérieur flatMap est la méthode de Tableau

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

Et l'intérieur flatMap est la fonction

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

Voici une simple comparaison des performances (compilée en mode Release). Il montre que la première méthode est plus rapide, environ une facteur sur 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
répondu Martin R 2015-04-25 21:38:08

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 c'est un peu moins intrusif.

0
répondu letvargo 2015-04-26 17:48:48

Comme c'est quelque chose que je semble finir par faire beaucoup, j'explore une fonction générique pour le faire.

J'ai essayé d'ajouter une extension au tableau pour pouvoir faire quelque chose comme possibles.unwraped mais je n'ai pas pu comprendre comment faire une extension sur un tableau. Au lieu personnalisé opérateur -- la partie la plus difficile ici est d'essayer de comprendre quel opérateur choisir. À la fin, j'ai choisi >! pour montrer que le tableau est filtré > puis déballé !.

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
répondu MathewS 2015-04-26 23:05:17

Lié à la question. Si vous appliquez flatMap à un tableau optionnel, n'oubliez pas de déballer ou de forcer le déballage 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 voulez 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
répondu Au Ris 2018-06-27 06:58:09