example array swift functional-programming optional higher-order-functions flatmap

array - Cómo utilizar swift flatMap para filtrar opciones de una matriz



reduce swift 4 (5)

Estoy un poco confundido con flatMap (agregado a Swift 1.2)

Digamos que tengo una matriz de algún tipo opcional, por ejemplo,

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

En Swift 1.1 haría un filtro seguido de un mapa como este:

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

He estado tratando de hacer esto usando flatMap de dos maneras:

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

y

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

Prefiero el último enfoque (porque no tengo que hacer un $0! desempaque forzado) $0! Estoy aterrorizado por estos y los evito a toda costa, excepto que necesito especificar el tipo de Array.

¿Hay alguna alternativa que resuelva el tipo por contexto, pero no tenga el desenvolvimiento forzado?


Con Swift 2 b1, simplemente puedes hacer

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

Para versiones anteriores, puedes agregar esto con la siguiente extensión:

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

Una advertencia (que también es cierta para Swift 2) es que puede que necesite escribir explícitamente el valor de retorno de la transformación:

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

Para obtener más información, consulte http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/


Podrías usar reduce :

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

Aún estás declarando el tipo, pero es un poco menos molesto.


Relacionado con la pregunta. Si está aplicando flatMap a una matriz opcional, no olvide flatMap o forzar el desenvolvimiento de su matriz, de lo contrario llamará a flatMap en Optional y no a los objetos que se ajusten al protocolo de Sequence . Cometí ese error una vez, por ejemplo, cuando quieres eliminar cadenas vacías:

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


Todavía me gusta la primera solución, que crea una sola matriz intermedia. Se puede escribir un poco más compacto como

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

Pero es posible flatMap() sin anotación de tipo y sin desempaquetado forzado:

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

El flatMap externo es el método de matriz

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

y el flatMap interior es la función.

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

Aquí hay una comparación de rendimiento simple (compilada en modo Release). Muestra que el primer método es más rápido, aproximadamente por un factor 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


Ya que esto es algo que parece que termino haciendo bastante, estoy explorando una función genérica para hacer esto.

Intenté agregar una extensión a Array para poder hacer algo como possibles.unwraped pero no pude averiguar cómo hacer una extensión en un Array. En su lugar usó un operador personalizado, la parte más difícil aquí fue tratar de averiguar qué operador elegir. Al final elegí >! para mostrar que la matriz se está filtrando > y luego se desenvuelve ! .

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]