swift list-comprehension

Enumerar la comprensión en Swift



list-comprehension (7)

La guía de idiomas no ha revelado ningún rastro de comprensión de la lista. ¿Cuál es la mejor manera de lograr esto en Swift? Estoy buscando algo similar a:

evens = [ x for x in range(10) if x % 2 == 0]


A partir de Swift 2 puedes hacer algo como esto:

var evens = [Int]() for x in 1..<10 where x % 2 == 0 { evens.append(x) } // or directly filtering Range due to default implementations in protocols (now a method) let evens = (0..<10).filter{ $0 % 2 == 0 }


A partir de Swift 2.x, hay unos pocos equivalentes breves a su comprensión de la lista al estilo de Python.

Las adaptaciones más directas de la fórmula de Python (que dice algo como "aplicar una transformación a una secuencia sujeta a un filtro") implican encadenar el map y filter métodos de filter disponibles para todos los SequenceType , y comenzando desde un Range :

// Python: [ x for x in range(10) if x % 2 == 0 ] let evens = (0..<10).filter { $0 % 2 == 0 } // Another example, since the first with ''x for x'' doesn''t // use the full ability of a list comprehension: // Python: [ x*x for x in range(10) if x % 2 == 0 ] let evenSquared = (0..<10).filter({ $0 % 2 == 0 }).map({ $0 * $0 })

Tenga en cuenta que un Range es abstracto; en realidad, no crea la lista completa de valores que usted solicita, solo un constructo que los suministra de forma perezosa a pedido. (En este sentido, se parece más al xrange de Python). Sin embargo, la llamada de filter devuelve una Array , por lo que se pierde el aspecto "flojo" allí. Si quiere mantener la colección floja durante todo el proceso, solo dígalo:

// Python: [ x for x in range(10) if x % 2 == 0 ] let evens = (0..<10).lazy.filter { $0 % 2 == 0 } // Python: [ x*x for x in range(10) if x % 2 == 0 ] let evenSquared = (0..<10).lazy.filter({ $0 % 2 == 0 }).map({ $0 * $0 })

A diferencia de la sintaxis de comprensión de listas en Python (y construcciones similares en algunos otros lenguajes), estas operaciones en Swift siguen la misma sintaxis que otras operaciones. Es decir, es el mismo estilo de sintaxis para construir, filtrar y operar en un rango de números, ya que es para filtrar y operar en una matriz de objetos; no es necesario usar la sintaxis de función / método para un tipo de trabajo y enumerar la sintaxis de comprensión para otro.

Y puede pasar otras funciones al filter y map llamadas, y encadenar otras transformaciones útiles como sort y reduce :

// func isAwesome(person: Person) -> Bool // let people: [Person] let names = people.filter(isAwesome).sort(<).map({ $0.name }) let sum = (0..<10).reduce(0, combine: +)

Dependiendo de lo que vayas, sin embargo, puede haber formas más concisas de decir lo que quieres decir. Por ejemplo, si desea específicamente una lista de enteros pares, puede usar stride :

let evenStride = 0.stride(to: 10, by: 2) // or stride(through:by:), to include 10

Al igual que con los rangos, esto te proporciona un generador, por lo que querrás crear una Array partir de él o recorrerlo para ver todos los valores:

let evensArray = Array(evenStride) // [0, 2, 4, 6, 8]

Editar: muy revisado para Swift 2.x. Consulte el historial de edición si desea Swift 1.x.


Con Swift 3, de acuerdo con sus necesidades o gustos, puede elegir uno de los siete códigos de Playground que son equivalentes a la comprensión de la lista de Python.

# 1. Uso de la función stride(from:to:by:)

let sequence = stride(from: 0, to: 10, by: 2) let evens = Array(sequence) // let evens = sequence.map({ $0 }) // also works print(evens) // prints [0, 2, 4, 6, 8]

# 2. Usando el método de filter(_:) CountableRange filter(_:)

let range = 0 ..< 10 let evens = range.filter({ $0 % 2 == 0 }) print(evens) // prints [0, 2, 4, 6, 8]

# 3. Usando el método flatMap(_:)

let range = 0 ..< 10 let evens = range.flatMap({ $0 % 2 == 0 ? $0 : nil }) print(evens) // prints [0, 2, 4, 6, 8]

# 4. Usar la función de sequence(first:next:)

let unfoldSequence = sequence(first: 0, next: { $0 + 2 < 10 ? $0 + 2 : nil }) let evens = Array(unfoldSequence) // let evens = unfoldSequence.map({ $0 }) // also works print(evens) // prints [0, 2, 4, 6, 8]

# 5. Uso del AnySequence init(_:) AnySequence init(_:)

let anySequence = AnySequence<Int>({ () -> AnyIterator<Int> in var value = 0 return AnyIterator<Int> { defer { value += 2 } return value < 10 ? value : nil } }) let evens = Array(anySequence) // let evens = anySequence.map({ $0 }) // also works print(evens) // prints [0, 2, 4, 6, 8]

# 6. Usando for loop con where cláusula

var evens = [Int]() for value in 0 ..< 10 where value % 2 == 0 { evens.append(value) } print(evens) // prints [0, 2, 4, 6, 8]

# 7. Usando for loop con if condition

var evens = [Int]() for value in 0 ..< 10 { if value % 2 == 0 { evens.append(value) } } print(evens) // prints [0, 2, 4, 6, 8]


Generalmente, una lista de comprensión en Python se puede escribir en la forma:

[f(x) for x in xs if g(x)]

Que es lo mismo que

map(f, filter(g, xs))

Por lo tanto, en Swift puedes escribirlo como

listComprehension<Y>(xs: [X], f: X -> Y, g: X -> Bool) = map(filter(xs, g), f)

Por ejemplo:

map(filter(0..<10, { $0 % 2 == 0 }), { $0 })


Tengo que admitir que estoy sorprendido de que nadie haya mencionado flatmap, ya que creo que es lo más cercano que tiene Swift para listar (o establecer o dictar) la comprensión.

var evens = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in if num % 2 == 0 {return num} else {return nil} })

Flatmap realiza un cierre, y puede devolver valores individuales (en cuyo caso devolverá una matriz con todos los valores no nulos y descartar los nils) o devolver segmentos de matriz (en cuyo caso cateinará todos sus segmentos juntos) y devolver eso.)

Flatmap parece en su mayoría (¿siempre?) No poder inferir valores de retorno. Ciertamente, en este caso no puede, entonces lo especifico como -> Int? para que pueda devolver nils, y así descartar los elementos impares.

Puede anidar mapas planos si lo desea. Y los encuentro mucho más intuitivos (aunque obviamente también un poco más limitados) que la combinación de mapa y filtro. Por ejemplo, la respuesta superior es ''evens squared'', usando flatmap, se convierte en

var esquares = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in if num % 2 == 0 {return num * num} else {return nil} })

La sintaxis no es exactamente una línea, no es exactamente lo mismo que todo lo demás, como lo es python. No estoy seguro si me gusta menos (porque para los casos simples en Python es muy corto y todavía muy legible) o más (porque los casos complejos pueden descontrolarse, y los programadores de Python experimentados a menudo piensan que están perfectamente legible y mantenible cuando un principiante en la misma empresa puede tomar media hora para descifrar lo que se pretendía hacer, y mucho menos lo que realmente está haciendo).

Here está la versión de flatMap desde la que devuelve elementos únicos o nulos, y here está la versión de la que devuelve segmentos.

Probablemente también valga la pena revisar tanto array.map como array.forEach, ya que ambos también son bastante útiles.


Un aspecto de la comprensión de listas que no se ha mencionado en este hilo es el hecho de que puede aplicarlo al producto cartesiano de múltiples listas. Ejemplo en Python:

[x + y for x in range(1,6) for y in range(3, 6) if x % 2 == 0]

... o Haskell:

[x+y | x <- [1..5], y <- [3..5], x `mod` 2 == 0]

En Swift, la lógica equivalente de 2 listas es

list0 .map { e0 in list1.map { e1 in (e0, e1) } } .joined() .filter(f) .map(g)

Y tendríamos que aumentar el nivel de anidación a medida que aumenta la cantidad de listas en la entrada.

Hace poco construí una pequeña library para resolver este problema (si lo considera un problema). Siguiendo mi primer ejemplo, con la biblioteca obtenemos

Array(1...5, 3...5, where: { n, _ in n % 2 == 0}) { $0 + $1 }

El fundamento (y más sobre la comprensión de la lista en general) se explica en una publicación de blog .


Una forma sería:

var evens: Int[]() for x in 0..<10 { if x%2 == 0 {evens += x} // or evens.append(x) }