20

ich ein wenig verwirrt um flatMap bin (hinzugefügt Swift 1,2)Wie rasche flatMap verwenden optionals zum Ausfiltern von einem Array

sagen, dass ich eine Reihe von Extras Typ haben z

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

In Swift 1.1 ich von einer Karte wie folgt ein Filter gefolgt tun würde:

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

ich versucht habe diese ein paar Möglichkeiten, mit flatMap zu tun:

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

und

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

Ich bevorzuge die letzte appr oach (weil ich keine erzwungene Unwrap machen muss $0! ... Ich habe Angst für diese und vermeiden Sie um jeden Preis) außer dass ich den Array-Typ angeben muss.

Gibt es eine Alternative, die den Typ durch Kontext ermittelt, aber nicht die erzwungene Unwrap?

+0

Sie wahrscheinlich Swift 1.2 vs 1.1 gemeint, es gibt keine Swift 1.3 noch ist (oder habe ich etwas verpasst?) –

+0

Upps, ja ich glaube, Xcode hatte 6.3 auf meinem Kopf ... aktualisiert Frage - Danke! – MathewS

+1

Obwohl du * flatMap {$ 0} 'benutzen kannst, um Nils zu entfernen, ist die wirkliche Frage * solltest * du. Wenn Sie nicht vorsichtig sind, kann 'flatMap' zu Fehlern führen, daher empfehle ich stattdessen [removeNils] (http://Stackoverflow.com/a/38548106/35690). – Senseful

Antwort

33

Mit Swift 2 b1, können Sie einfach tun

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

Für frühere Versionen, können Sie dies mit folgenden Erweiterung Shim:

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

Einen Nachteil (was auch wahr ist für Swift 2) ist, dass Sie möglicherweise explizit den Rückgabewert der Transformation eingeben müssen:

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

Weitere Informationen finden Sie http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/

+0

Ich sah flatMap, wusste aber nicht, dass Sie es verwenden könnten, um solche Optionen zu filtern. Wirklich schöne Ergänzung zu Swift 2! – MathewS

0

könnten Sie reduce verwenden:

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

Sie sind von immer noch irgendwie die Art erklären, aber es ist etwas weniger aufdringlich.

+0

Das ist cleverer Gebrauch von reduzieren, aber (für mich) braucht zu viel geistige Entfaltung, um zu sehen, was passiert. Danke für die Idee! – MathewS

14

Ich mag immer noch die erste Lösung, die nur eine Zwischen Array erstellt. Es kann etwas kompakter als

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

Aber flatMap() ohne Typanmerkung geschrieben werden und ohne Zwangs Abwickeln möglich:

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

Die äußere flatMap ist die Array-Methode

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

und die innere flatMap ist die Funktion

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

Hier ist ein einfacher Performance-Vergleich (in Release-Modus kompilierte). Es zeigt, dass die erste Methode ist schneller, etwa um den Faktor von 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 
+0

Danke! Da die Filter/Map-Option schneller ist, bleibe ich dabei, da (für mich) am deutlichsten ist, was passiert. – MathewS

+0

in swift 2 können Sie auch einfach 'movies.flatmap {$ 0}' gehen. Siehe Dokumentation: 'public func flatMap (@noescape transform: (Self.Generator.Element) wirft -> T?) Rettrows -> [T]' Flatmap gibt ein 'Array' zurück, das die Nicht-Null-Ergebnisse des Mappings' transform' enthält über "Selbst". –

+0

@DanielGalasko: Ja, das schreibt Fizker in der (jetzt) ​​akzeptierten Antwort. Ich denke, das war nicht verfügbar, als ich diese Antwort schrieb. –

0

Da es sich um etwas, das ich scheinen am Ende tut ziemlich viel ich eine generische Funktion zu erforschen, dies zu tun.

Ich habe versucht, eine Erweiterung zu Array hinzufügen, so dass ich etwas wie possibles.unwraped tun konnte, aber nicht herausfinden, wie eine Erweiterung auf einem Array zu machen. Stattdessen wurde ein benutzerdefinierter Operator verwendet - der schwierigste Teil hier versuchte herauszufinden, welcher Operator ausgewählt werden sollte. Am Ende wählte ich >! um zu zeigen, dass das Array gefiltert wird > und dann ausgepackt !.

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]