Escrito por Reinder de Vries el 9 de julio de 2020 en Desarrollo de aplicaciones, Swift

Mapear, reducir y filtrar en Swift

En Swift se utiliza map(), reduce() y filter() para realizar bucles en colecciones como matrices y diccionarios, sin utilizar un bucle for.

Las funciones de mapa, reducción y filtro provienen del ámbito de la programación funcional (FP). Se llaman funciones de orden superior, porque toman funciones como entrada. Está aplicando una función a una matriz, por ejemplo, para transformar sus datos.

Las funciones de mapa, Reducción y filtro de Swift pueden ser difíciles de entender. Especialmente si siempre has codificado bucles for in para resolver problemas de iteración. En esta guía, aprenderá a utilizar las funciones map(_:), reduce(_:_:) y filter(_:) en Swift.

¡Comencemos!

  1. Introducción a Mapear, Reducir y Filtrar
  2. Inicio rápido: Funciones de orden superior en Swift
  3. Usando la Función de Mapa
  4. Usando la Función de Reducción
  5. Usando la Función de filtro
  6. Combinando Mapa, Reducir y Filtrar
  7. Lectura adicional

Introducción a Map, Reducir y Filtrar

Cuando está creando iOS aplicaciones, normalmente se utiliza programación orientada a objetos o de procedimiento. La programación funcional es diferente: solo trata de funciones. Sin variables, sin estado — sin bucles for, solo funciones.

El lenguaje de programación Swift se presta perfectamente para mezclar programación funcional con enfoques no funcionales, como OOP. No necesita escribir código funcional estrictamente, y la adopción de conceptos de programación funcional puede ayudarlo a aprender a codificar mejor.

Las funciones map(_:), reduce(_:_:) y filter(_:) se denominan funciones de orden superior, porque toman una función como entrada y devuelven funciones como salida. Técnicamente, Swift devuelve los resultados de una operación (p. ej. una matriz transformada) cuando se utilizan funciones de orden superior, mientras que un lenguaje funcional puro devolverá una colección de funciones. En Swift, las entradas para estas funciones son cierres.

Así es como funcionan:

  • La función map() aplica una función a cada elemento de una colección. Piense en «mapear» o transformar un conjunto de valores en otro conjunto de valores.
  • La función reduce() convierte una colección en un valor. Piense en ello como combinar muchos valores en uno, como promediar un conjunto de números.
  • La función filter() simplemente devuelve valores que pasaron una instrucción if, y solo si esa condición resultó en true.

En caso de que estés pensando: «Mira, no necesito programación funcional o procesamiento de datos, ¡porque mis aplicaciones no hacen eso!»entonces no te detengas aquí. En proyectos de aplicaciones recientes, he utilizado Map, Reduce y Filter en múltiples ocasiones:

  • Filtrar los valores de coste / ingresos con filter(_:), para cumplir un umbral, antes de mostrar los valores en un gráfico de líneas
  • Promediando miles de clasificaciones de películas en un valor con reduce(_:_:)
  • Mapear algunas operaciones en una cadena con hashtags, transformándola en una colección normalizada, con map(_:)

Podrías haber resuelto todos estos problemas con un bucle for, pero verás que usar map(), reduce() y filter() da como resultado un código más conciso, legible y de mayor rendimiento.

Inicio Rápido: Funciones de orden superior en Swift

Nos centraremos en map(), reduce() y filter() en este tutorial. Antes de seguir adelante, aquí encontrará un resumen rápido de las funciones de orden superior más comunes en Swift:

  • map(_:) bucles sobre cada elemento de una secuencia, aplica una función a cada elemento y devuelve el resultado transformado
  • reduce(_:_:) bucles sobre cada elemento de una secuencia, los combina en un valor y devuelve el resultado combinado
  • filter(_:) bucles sobre cada elemento de una secuencia y devuelve una secuencia resultante que solo contiene elementos que satisfacen una función de filtrado dada
  • flatMap(_:) hace lo mismo que map(_:), excepto que aplana la secuencia resultante, i. e. los arrays anidados no anidados o «aplanados»»
  • compactMap(_:) hace lo mismo que map(_:), excepto que elimina valores nil de la secuencia resultante antes de devolverla

Puede usar estas funciones en matrices, diccionarios, conjuntos, rangos, secuencias y cualquier otro tipo Swift que pueda iterar. Si desea obtener más información sobre compactMap(_:) y flatMap(_:), consulte este tutorial.

Varios tipos de Swift, como Array y Dictionary, tienen funciones que aceptan cierres como entrada. Una selección rápida:

  • contains(where:) bucles sobre una colección, aplica un predicado (un cierre) a cada elemento, devuelve un true si un elemento satisface el predicado, de lo contrario false
  • first(where:) recorre una colección, aplica un predicado (un cierre) a cada elemento y devuelve el elemento si satisface el predicado
  • firstIndex(where:) hace lo mismo que first(where:), excepto que devuelve el índice en lugar del valor

Puede obtener más información sobre estas funciones en este tutorial. El uso de where en Swift también es interesante, puede obtener más información al respecto en este tutorial.

Ha visto las funciones» map «y» reduce » en Swift escritas como map(_:) y reduce(_:_:) a lo largo de este tutorial. Los guiones bajos y dos puntos en esas funciones son parte de la firma de la función, que es un formato especial para indicar los parámetros de la función. Por ejemplo, la función map(_:) tiene un parámetro sin nombre, mientras que la función reduce(_:_:) tiene dos. Puede obtener más información al respecto en este tutorial: Explicación de funciones en Swift.

Contrátese como desarrollador de iOS

Aprenda a crear aplicaciones de iOS 14 con Swift 5

Inscríbase en mi curso de desarrollo de iOS y aprenda a comenzar su carrera como desarrollador de iOS profesional.

Mediante la función Map

, la función map(_:) recorre en bucle cada elemento de una colección y aplica una operación a cada elemento de la colección. Devuelve una colección de elementos resultantes, a los que se aplicó la operación.

veamos un ejemplo. Tenemos una variedad de temperaturas en grados Celsius que desea transformar en grados Fahrenheit.

Podrías usar un bucle for, como este:

let celsius = var fahrenheit: = for value in celsius { fahrenheit += }print(fahrenheit)// Output: 

El código funciona bien, pero es demasiado detallado. Necesita una variable «auxiliar» mutable fahrenheit para almacenar las conversiones calculadas a medida que trabaja a través de ellas, y necesita 3 líneas de código para la conversión en sí.

Así es como podemos hacer lo mismo con la función map(_:) :

sea celsius =
sea fahrenheit = celsius.mapa { $0 * (9/5) + 32 }
impresión (fahrenheit)
Ocultar advertencias

Incluso puedes hacer todo eso en una sola línea:

.map {  * (9/5) + 32 }

¿Qué pasa aquí?

  1. Se define una constante celsius, una matriz de dobles e inicializada con unos pocos valores Celsius aleatorios.
  2. La función map(_:) se llama en el array celsius. La función tiene un argumento, un cierre, que convierte de Celsius a Fahrenheit.
  3. Finalmente, se imprime el resultado: la matriz convertida, de Celsius a Fahrenheit.

La función map(_:) transforma una matriz en otra, aplicando una función a cada elemento de la matriz. El cierre * (9/5) + 32 toma el valor de entrada en Celsius y devuelve un valor en Fahrenheit. La matriz resultante de map(_:) se construye a partir de esos valores convertidos.

Echemos un vistazo más de cerca al cierre. Si has trabajado con cierres antes, reconocerás la sintaxis de cierre a mano corta. Es una forma más corta de codificar un cierre, al omitir gran parte de su sintaxis.

Aquí hay una alternativa menos concisa:

let celsius = let fahrenheit = celsius.map({ (value: Double) -> Double in return value * (9/5) + 32})print(fahrenheit)

La llamada real a la función map(_:), y su cierre, es este:

··· = celsius.map({ (value: Double) -> Double in return value * (9/5) + 32})

¿Qué está pasando ahí? La función map(_:) se llama en el array celsius. Toma un argumento: un cierre de tipo (Double) -> Double.

La primera parte de la clausura, empezando con {, indica que este cierre tiene un parámetro value de tipo Double, y el cierre devuelve un valor de tipo Double. El cuerpo de cierre, a partir de return, simplemente devuelve el resultado del cálculo Celsius a Fahrenheit.

Si comparas la sintaxis de cierre a mano corta con el código expandido anterior, verás que:

  • Los paréntesis de función ( y ) se omiten, porque puede omitirlos cuando el último parámetro de una llamada a función es un cierre.
  • La parte () -> in se puede omitir, porque Swift puede inferir que está utilizando un parámetro Double como entrada, y se espera que devuelva un Double. Ahora que ha omitido la variable value, puede usar la mano corta .
  • La instrucción return también se puede omitir, porque se espera que este cierre devuelva el resultado de una expresión de todos modos.

Aunque el ejemplo de código anterior utiliza tipos Double, no está limitado a estos tipos. El tipo resultante de una función map() puede tener un tipo diferente al que se le agrega, y también puede usar map() en una Dictionary.

¡Pasemos a reduce(_:_:)!

Usando la función Reducir

La función reduce(_:_:) recorre todos los elementos de una colección y los reduce a un valor. Piense en ello como la combinación de múltiples valores en uno.

La función reducir es quizás la función de mapa, reducción y filtro más difícil de comprender. ¿Cómo se puede pasar de una colección de valores a un solo valor?

Algunos ejemplos:

  • Crear una suma de múltiples valores, es decir, 3 + 4 + 5 = 12
  • Concatenar una colección de cadenas, es decir, = "Zaphod, Trillian, Ford"
  • Promediar un conjunto de valores, es decir,(7 + 3 + 10) / 3 = 7/3 + 3/3 + 10/3 = 6.667

En el procesamiento de datos, puede imaginar muchos escenarios cuando operaciones simples como estas son útiles. Al igual que antes, puede resolver cualquiera de estos problemas con un bucle for, pero reduce(_:_:) es simplemente más corto y rápido.

Aquí está cómo:

let valores =
let suma = valores.reducir (0,+)
imprimir (suma)
Ocultar advertencias

La función reduce(_:_:) toma dos argumentos, un valor inicial y un cierre. En el código anterior, proporcionamos el operador +, que también es una función con dos parámetros.

También puede proporcionar su propio cierre, por supuesto:

let valores =
let promedio = valores.reducir(0.0) { $0 + $1 } / Doble(valores.recuento)
impresión (promedio)
Ocultar advertencias

En el ejemplo anterior, estás calculando el promedio de tres números. Los tipos de los valores en el código son todos Double. Primero sumamos todos los números, y luego los dividimos por la cantidad de números.

La función reduce(_:_:) es diferente de dos maneras:

  1. La función reduce(_:_:) tiene dos parámetros sin nombre; el valor inicial y el cierre
  2. El cierre que se proporciona a reduce(_:_:) también tiene dos parámetros; el resultado actual de la reducción y el nuevo valor que está a punto de reducirse

Aquí, mira esto:

let valores =
let suma = valores.reducir (0) {
imprimir(«\($0) + \($1) = \($0 + $1)»)
retorno $0 + $1
}
imprimir (suma)
Ocultar advertencias

En el ejemplo anterior, puede ver claramente y , los 2 parámetros de los cierres. Cuando ejecuta el código, esta es la salida que obtiene:

0 + 7 = 77 + 3 = 1010 + 10 = 2020

¿Ves cómo empezamos con 0 y luego agregamos 7? En el siguiente paso, tomaremos 7, el valor de reducción actual, y agregaremos 3, el valor «siguiente» en la reducción.

Aquí hay otra forma de verlo:

0 + 7(0 + 7) + 3((0 + 7) + 3) + 10

Esto también deja claro por qué necesita un valor inicial para reduce(_:_:), porque ese es el primer parámetro del cierre.

La reducción puede ser difícil de comprender. Es importante entender que estás aplicando iterativamente una operación, como +, a un conjunto de valores hasta que solo te queda un valor. Reduce literalmente la cantidad de valores.

¡Pasemos a filter(_:)!

Usando la función de filtro

La función filter recorre todos los elementos de una colección y devuelve una colección que contiene solo elementos que cumplen una condición de inclusión.

Es como aplicar una instrucción if-a una colección, y solo mantener los valores que pasan la condición.

Aquí, mira esto:

let values =
let even = valores.filtro { $0.Es múltiple (of: 2)}
impresión (par)
Ocultar advertencias

En el ejemplo anterior, está filtrando números de values que son pares. La función isMultiple(of:) devuelve true cuando se puede dividir por 2, y false de lo contrario.

A diferencia de map(_:) y reduce(_:_:) , el cierre filter(_:) necesita devolver un booleano, por lo que puede ser true o false. Cuando el cierre devuelve true, el valor se mantiene, y cuando se devuelve false, el valor se omite. Así es como filter(_:) filtra la matriz de entrada.

Este es un ejemplo un poco más claro, con el cierre expandido:

let values = let even = values.filter({ (value:Int) -> Bool in return value.isMultiple(of: 2)})print(even) // Output: 

En el ejemplo, el cierre devuelve un valor booleano, indicado con -> Bool. Se proporciona un parámetro, el elemento de la colección, y devuelve el resultado de isMultiple(of:). ¡Genial!

Combinar Map, Reducir y Filtrar

¿Puede combinar las funciones map(), reduce() y filter()? ¡Claro que puedes!

Digamos que tenemos una clase de estudiantes. Ya sabes el año en que nació cada estudiante. Desea calcular la edad combinada de todos los estudiantes nacidos en o después de 2000.

Así es como lo haces:

let now = 2020
let years =
let sum = years.filtro({ $0 >= 2000 }).mapa ({ ahora-now 0}).reducir (0,+)
imprimir (suma)
Ocultar advertencias

El ejemplo de código anterior utiliza encadenamiento. Utiliza el resultado de una función como entrada para otra, combinando map-reduce-filter. La función map() se llama en la matriz de resultados de la función filter(), y la función reduce() se llama en el resultado de la función map(). ¡Órale!

El código en sí es simple:

  1. Cree una constante now y years, y asígnele un montón de años.
  2. Filtrar los años que están por debajo de 2000, p. ej. mantenga los que >= 2000 es true
  3. Transforme cada año en una edad, restando el año de now
  4. Agregue todas las edades, reduciendo con +

Tomemos un ejemplo más interesante. Compruebe el código siguiente, tomado del tutorial sobre FizzBuzz:

vamos a fizzbuzz:(Int) -> String = { i in
interruptor (i % 3 == 0, i % 5 == 0)
{
caso (true, false):
volver «Fizz»
caso (falso, verdadero):
volver «Buzz»
caso (true, true):
volver «FizzBuzz»
por defecto:
retorno » \(i)»
}
}
let result = Array (2…100).mapa(fizzbuzz).reducir(«1», { $0 + «, » + $1 })
imprimir (resultado)
Ocultar advertencias

Ver ¿Qué está pasando? Estamos transformando una matriz con números del 2 al 100 en «Fizz», «Buzz» o «FizzBuzz» con map(_:), según las reglas del juego. Finalmente, estamos reduciendo esa matriz de cadenas en una cadena grande con reduce(_:_:), combinando todos los valores. ¡Genial!

Lectura adicional

¿Y si tuvieras que codificar todo esto con bucles for in? Usarías mucho más código. Y eso es el poder del filtro de reducción de mapas: es más conciso, a menudo más fácil de leer y, admítalo, ¡bastante genial!

¿Quieres saber más? Consulte estos recursos:

  • Cómo Usar «dónde» en Swift
  • Mapa plano Y Mapa compacto Explicados En Swift
  • La Guía Definitiva de Cierres en Swift
  • Cómo: Encontrar Un Elemento En Una Matriz En Swift
  • Jugar Con Código: Búsqueda Binaria En Swift
  • Comience con Xcode Playgrounds

Contrátese como desarrollador de iOS

Aprenda a crear aplicaciones de iOS 14 con Swift 5

Inscríbase en mi curso de desarrollo de iOS y aprenda a comenzar su carrera como desarrollador de iOS profesional.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.