Écrit par Reinder de Vries le 9 juillet 2020 dans Développement d’applications, Swift

 Mappez, réduisez et filtrez dans Swift

Dans Swift, vous utilisez map(), reduce() et filter() pour parcourir des collections telles que des tableaux et des dictionnaires, sans utiliser de boucle for.

Les fonctions map, reduce et filter proviennent du domaine de la programmation fonctionnelle (FP). On les appelle des fonctions d’ordre supérieur, car elles prennent des fonctions en entrée. Vous appliquez une fonction à un tableau, par exemple, pour transformer ses données.

Les fonctions de cartographie, de réduction et de filtrage de Swift peuvent être difficiles à contourner. Surtout si vous avez toujours codé des boucles for in pour résoudre les problèmes d’itération. Dans ce guide, vous apprendrez à utiliser les fonctions map(_:), reduce(_:_:) et filter(_:) dans Swift.

Commençons!

  1. Introduction à Cartographier, Réduire et filtrer
  2. Démarrage rapide: Fonctions d’ordre supérieur dans Swift
  3. Utilisation de la Fonction Map
  4. Utilisation de la Fonction Reduce
  5. Utilisation de la Fonction Filter
  6. Combinaison de Map, Reduce et Filter
  7. Lectures supplémentaires

Introduction à Map, Reduce et Filter

Lorsque vous construisez iOS applications, vous utilisez généralement une programmation procédurale ou orientée objet. La programmation fonctionnelle est différente: elle ne traite que des fonctions. Pas de variables, pas d’état, pas de boucles for – juste des fonctions.

Le langage de programmation Swift se prête parfaitement au mélange de programmation fonctionnelle avec des approches non fonctionnelles, comme la POO. Vous n’avez pas besoin d’écrire strictement du code fonctionnel, et l’adoption de concepts de programmation fonctionnelle peut vous aider à apprendre à mieux coder.

Les fonctions map(_:), reduce(_:_:) et filter(_:) sont appelées fonctions d’ordre supérieur, car elles prennent une fonction en entrée et des fonctions de retour en sortie. Techniquement, Swift renvoie les résultats d’une opération (i.e. un tableau transformé) lors de l’utilisation de fonctions d’ordre supérieur, alors qu’un langage fonctionnel pur renverra une collection de fonctions. Dans Swift, les entrées de ces fonctions sont des fermetures.

Voici comment ils fonctionnent:

  • La fonction map() applique une fonction à chaque élément d’une collection. Pensez à « cartographier » ou à transformer un ensemble de valeurs en un autre ensemble de valeurs.
  • La fonction reduce() transforme une collection en une seule valeur. Pensez-y comme combinant plusieurs valeurs en une seule, comme faire la moyenne d’un ensemble de nombres.
  • La fonction filter() renvoie simplement les valeurs qui ont passé une instruction if, et seulement si cette condition a entraîné true.

Au cas où vous penseriez: « Écoutez, je n’ai pas besoin de programmation fonctionnelle ou de traitement de données, car mes applications ne le font pas! »alors ne vous arrêtez pas là. Dans les projets d’applications récents, j’ai utilisé Map, Reduce et Filter à plusieurs reprises:

  • Filtrage des valeurs coûts / revenus avec filter(_:), pour atteindre un seuil, avant d’afficher les valeurs dans un graphique linéaire
  • Moyenne de milliers d’évaluations de films en une seule valeur avec reduce(_:_:)
  • Mappage de quelques opérations sur une chaîne avec des hashtags, la transformant en une collection normalisée, avec map(_:)

Vous auriez pu résoudre tous ces problèmes avec une boucle for, mais vous verrez que l’utilisation de map(), reduce() et filter() aboutit à un code plus concis, lisible et performant.

Démarrage rapide: Fonctions d’ordre supérieur dans Swift

Nous nous concentrerons sur map(), reduce() et filter() dans ce tutoriel. Avant de passer à autre chose, voici un aperçu rapide des fonctions d’ordre supérieur les plus courantes dans Swift:

  • map(_:) boucle sur chaque élément d’une séquence, applique une fonction à chaque élément et renvoie le résultat transformé
  • reduce(_:_:) boucle sur chaque élément d’une séquence, les combine en une seule valeur et renvoie le résultat combiné
  • filter(_:) boucle sur chaque élément d’une séquence et renvoie une séquence résultante qui ne contient que des éléments qui satisfont une fonction de filtrage donnée
  • flatMap(_:) fait la même chose que map(_:), sauf qu’il aplatit la séquence résultante, c’est-à-dire les tableaux imbriqués sont non imbriqués ou  » aplatis »
  • compactMap(_:) fait la même chose que map(_:), sauf qu’il supprime nil valeurs de la séquence résultante avant de la renvoyer

Vous pouvez utiliser ces fonctions sur des tableaux, des dictionnaires, des ensembles, des plages, des séquences et tout autre type Swift que vous pouvez parcourir. Si vous souhaitez en savoir plus sur compactMap(_:) et flatMap(_:), consultez ce tutoriel.

Plusieurs types de Swift, tels que Array et Dictionary, ont des fonctions qui acceptent les fermetures en entrée. Un choix rapide:

  • contains(where:) boucle sur une collection, applique un prédicat (une fermeture) à chaque élément, renvoie un true si un élément satisfait le prédicat, sinon false
  • first(where:) boucle sur une collection, applique un prédicat (une fermeture) à chaque élément et renvoie l’élément s’il satisfait le prédicat
  • firstIndex(where:) fait la même chose que first(where:), sauf qu’il renvoie l’index au lieu de la valeur

Vous pouvez en savoir plus sur ces fonctions dans ce tutoriel. Comment where est utilisé dans Swift est également intéressant, vous pouvez en apprendre plus à ce sujet dans ce tutoriel.

Vous avez vu les fonctions « map » et « reduce » dans Swift écrites comme map(_:) et reduce(_:_:) tout au long de ce tutoriel. Les traits de soulignement et les deux-points dans ces fonctions font partie de la signature de la fonction, qui est un format spécial pour indiquer les paramètres de la fonction. Par exemple, la fonction map(_:) a un paramètre sans nom, tandis que la fonction reduce(_:_:) en a deux. Vous pouvez en apprendre plus à ce sujet dans ce tutoriel: Les fonctions de Swift expliquées.

Soyez embauché en tant que développeur iOS

Apprenez à créer des applications iOS 14 avec Swift 5

Inscrivez-vous à mon cours de développement iOS et apprenez à démarrer votre carrière en tant que développeur iOS professionnel.

En utilisant la fonction de carte

La fonction map(_:) boucle sur chaque élément d’une collection et applique une opération à chaque élément de la collection. Il renvoie une collection d’éléments résultants auxquels l’opération a été appliquée.

Regardons un exemple. Nous avons un tableau de températures en Celsius que vous voulez transformer en Fahrenheit.

Vous pouvez utiliser une boucle for, comme ceci:

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

Le code fonctionne bien, mais il est trop verbeux. Vous avez besoin d’une variable « helper » mutable fahrenheit pour stocker les conversions calculées au fur et à mesure que vous les parcourez, et vous avez besoin de 3 lignes de code pour la conversion elle-même.

Voici comment nous pouvons faire de même avec la fonction map(_:):

laissez celsius =
laissez fahrenheit= celsius.carte { $0 * (9/5) + 32 }
imprimer (fahrenheit)
Masquer les avertissements

Vous pouvez même faire tout cela sur une seule ligne:

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

Que se passe-t-il ici ?

  1. Une constante celsius est définie, un tableau de doubles, et initialisée avec quelques valeurs Celsius aléatoires.
  2. La fonction map(_:) est appelée sur le tableau celsius. La fonction a un argument, une fermeture, qui convertit de Celsius en Fahrenheit.
  3. Enfin, le résultat est imprimé: le tableau converti, de Celsius à Fahrenheit.

La fonction map(_:) transforme un tableau en un autre, en appliquant une fonction à chaque élément du tableau. La fermeture * (9/5) + 32 prend la valeur d’entrée en Celsius et renvoie une valeur en Fahrenheit. Le tableau résultant de map(_:) est construit à partir de ces valeurs converties.

Regardons de plus près la fermeture. Si vous avez déjà travaillé avec des fermetures, vous reconnaîtrez la syntaxe de fermeture à main courte. C’est un moyen plus court de coder une fermeture, en omettant une grande partie de sa syntaxe.

Voici une alternative moins concise:

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

L’appel de fonction map(_:) réel et sa fermeture sont les suivants:

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

Que se passe-t-il là-bas ? La fonction map(_:) est appelée sur le tableau celsius. Il faut un argument : une fermeture de type (Double) -> Double.

La première partie de la fermeture, commençant par {, indique que cette fermeture a un paramètre value de type Double, et la fermeture renvoie une valeur de type Double. Le corps de fermeture, commençant par return, renvoie simplement le résultat du calcul Celsius en Fahrenheit.

Si vous comparez la syntaxe de fermeture à main courte au code développé ci-dessus, vous verrez que:

  • Les parenthèses de fonction ( et ) sont omises, car vous pouvez les laisser de côté lorsque le dernier paramètre d’un appel de fonction est une fermeture.
  • La partie () -> in peut être omise, car Swift peut déduire que vous utilisez un paramètre Double en entrée et que vous êtes censé renvoyer un Double. Maintenant que vous avez omis la variable value, vous pouvez utiliser l’aiguille courte .
  • L’instruction return peut également être omise, car cette fermeture devrait de toute façon renvoyer le résultat d’une expression.

Même si l’exemple de code ci-dessus utilise des types Double, vous n’êtes pas limité à ces types. Le type résultant d’une fonction map() peut avoir un type différent de celui que vous y mettez, et vous pouvez également utiliser map() sur une fonction Dictionary.

Passons à reduce(_:_:)!

Utilisation de la fonction de réduction

La fonction reduce(_:_:) boucle chaque élément d’une collection et les réduit à une valeur. Pensez-y comme combinant plusieurs valeurs en une seule.

La fonction reduce est peut-être la plus difficile de map, reduce, filter à comprendre. Comment passer d’une collection de valeurs à une seule valeur ?

Quelques exemples:

  • Créer une somme de plusieurs valeurs, c’est-à-dire 3 + 4 + 5 = 12
  • Concaténer une collection de chaînes, c’est-à-dire = "Zaphod, Trillian, Ford"
  • Moyenner un ensemble de valeurs, c’est-à-dire (7 + 3 + 10) / 3 = 7/3 + 3/3 + 10/3 = 6.667

Dans le traitement des données, vous pouvez imaginer de nombreux scénarios lorsque des opérations simples comme celles-ci sont utiles. Comme auparavant, vous pouvez résoudre l’un de ces problèmes avec une boucle for, mais reduce(_:_:) est simplement plus court et plus rapide.

Voici comment:

let values=
let sum=valeurs.réduire (0, +)
imprimer (somme)
Masquer les avertissements

La fonction reduce(_:_:) prend deux arguments, une valeur initiale et une fermeture. Dans le code ci-dessus, nous fournissons l’opérateur +, c’est aussi une fonction avec deux paramètres.

Vous pouvez également fournir votre propre fermeture, bien sûr:

let values=
let average= valeurs.réduire (0.0) { $0 + $1 } / Double (valeurs.nombre)
impression (moyenne)
Masquer les avertissements

Dans l’exemple ci-dessus, vous calculez la moyenne de trois nombres. Les types des valeurs dans le code sont tous Double. Nous additionnons d’abord tous les nombres, puis nous les divisons par le nombre de nombres.

La fonction reduce(_:_:) est différente de deux manières:

  1. La fonction reduce(_:_:) a deux paramètres sans nom ; la valeur initiale et la fermeture
  2. La fermeture fournie à reduce(_:_:) a également deux paramètres; le résultat actuel de la réduction et la nouvelle valeur sur le point d’être réduite

Ici, vérifiez ceci:

let values=
let sum=values.reduce(0) {
imprimer(« \($0) + \($1) = \($0 + $1) »)
retour $0 + $1
}
imprimer (somme)
Masquer les avertissements

Dans l’exemple ci-dessus, vous pouvez clairement voir et , les 2 paramètres des fermetures. Lorsque vous exécutez le code, c’est la sortie que vous obtenez:

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

Voyez comment nous commençons par 0, puis ajoutons 7? Dans l’étape suivante, nous prenons 7 – la valeur de réduction actuelle – et ajoutons 3, la valeur « suivante » de la réduction.

Voici une autre façon de le voir:

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

Cela explique également pourquoi vous avez besoin d’une valeur initiale pour reduce(_:_:), car c’est le premier premier paramètre de la fermeture.

La réduction peut être difficile à saisir. Il est important de comprendre que vous appliquez de manière itérative une opération, comme +, à un ensemble de valeurs jusqu’à ce qu’il ne vous reste qu’une seule valeur. Vous réduisez littéralement la quantité de valeurs.

Passons à filter(_:)!

Utilisation de la fonction de filtre

La fonction filter boucle sur chaque élément d’une collection et renvoie une collection contenant uniquement des éléments qui satisfont à une condition d’inclusion.

C’est comme appliquer une instruction if à une collection et ne conserver que les valeurs qui transmettent la condition.

Ici, vérifiez ceci:

let values=
let even=values.filtre {00.isMultiple(de:2) }
print(même)
Masquer les avertissements

Dans l’exemple ci-dessus, vous filtrez les nombres de values qui sont pairs. La fonction isMultiple(of:) renvoie true lorsque peut être divisé par 2 et false sinon.

Contrairement à map(_:) et reduce(_:_:), la fermeture filter(_:) doit renvoyer un booléen, donc true ou false. Lorsque la fermeture renvoie true, la valeur est conservée et lorsque false est renvoyée, la valeur est omise. C’est ainsi que filter(_:) filtre le tableau d’entrée.

Voici un exemple légèrement plus clair, avec la fermeture élargie:

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

Dans l’exemple, la fermeture renvoie une valeur booléenne, indiquée par -> Bool. Il fournit un paramètre, l’élément de la collection, et renvoie le résultat de isMultiple(of:). Soigné!

Combinaison des fonctions Map, Reduce et Filter

Pouvez-vous combiner les fonctions map(), reduce() et filter() ? Bien sûr que vous pouvez!

Disons que nous avons une classe d’étudiants. Vous savez l’année de naissance de chaque élève. Vous souhaitez calculer l’âge combiné de tous les étudiants nés en 2000 ou après.

Voici comment vous faites cela:

let now =2020
let years =
let sum =years.filtre({ $0 >= 2000 }).carte ({maintenant -00}).réduire (0, +)
imprimer (somme)
Masquer les avertissements

L’exemple de code ci-dessus utilise le chaînage. Il utilise le résultat d’une fonction comme entrée pour une autre, en combinant map-reduce-filter. La fonction map() est appelée sur le tableau de résultats de la fonction filter(), et la fonction reduce() est appelée sur le résultat de la fonction map(). Fantastique!

Le code lui-même est simple:

  1. Faites une constante now et years, et attribuez-lui un tas d’années.
  2. Filtrez les années inférieures à 2000, c’est-à-dire gardez ceux pour lesquels >= 2000 est true
  3. Transformez chaque année en âge, en soustrayant l’année de now
  4. Additionnez tous les âges, en réduisant avec +

Prenons un exemple plus intéressant. Découvrez le code suivant, tiré du tutoriel sur FizzBuzz:

soit fizzbuzz: (Int) – > String = {i dans
switch (i%3 == 0, i % 5 == 0)
{
case (true, false):
return « Fizz »
case(false, true):
return « Buzz »
case (true, true):
return « FizzBuzz »
default:
retour « \(i) »
}
}
soit result= Array(2…100).carte (fizzbuzz).réduire(« 1 », { $0 + « ,  » + $1 })
imprimer (résultat)
Masquer les avertissements

Voir ce qui se passe ? Nous transformons un tableau avec des nombres de 2 à 100 en « Fizz », « Buzz » ou « FizzBuzz » avec map(_:), en fonction des règles du jeu. Enfin, nous réduisons ce tableau de chaînes en une seule grande chaîne avec reduce(_:_:), combinant chaque valeur. Soigné!

Pour en savoir plus

Et si vous deviez coder tout cela avec des boucles for in? Vous utiliseriez beaucoup plus de code. Et c’est la puissance de map-reduce-filter: c’est plus concis, souvent plus facile à lire, et — avouez—le – sacrément cool!

Vous voulez en savoir plus ? Consultez ces ressources:

  • Comment Utiliser « where » dans Swift
  • FlatMap Et CompactMap Expliqués Dans Swift
  • Le Guide Ultime des Fermetures dans Swift
  • Comment: Trouver Un Élément Dans Un Tableau Dans Swift
  • Jouer Avec le Code: Recherche Binaire Dans Swift
  • Commencez avec Xcode Playgrounds

Soyez embauché en tant que développeur iOS

Apprenez à créer des applications iOS 14 avec Swift 5

Inscrivez-vous à mon cours de développement iOS et apprenez à démarrer votre carrière en tant que développeur iOS professionnel.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.