Map, reduce, filter, forEach et flatMap

Aujourd’hui nous allons parler de méthode ou plus exactement de fonction d’ordre supérieur très utiles pour manipuler les collections en Swift aux travers plusieurs examples. Une fonction d’ordre supérieur est simplement une fonction qui prend en paramètre une ou plusieurs autres fonctions et / ou retourne une fonction. Les méthodes que nous allons étudier dans ce tutoriel se résument principalement à ces 3 là : map, reduce, filter, sans oublié forEach et flatMap.

Tout d’abord voici une petite description rapide de chacune de ces méthodes :

  • map : permet de transformer une collection en une liste.
  • reduce : permet de transformer une collection en autre chose.
  • filter : permet de garder seulement certains éléments d’une collection.
  • forEach : permet d’itérer au travers des éléments d’une collection.
  • flatMap : est équivalent à la fonction map tout en « aplatissant » le résultat (on verra plus loin ce que cela veut dire).

Maintenant nous allons regarder dans le détail chacune de ces fonctions.

Map

Tout d’abord voici la signature de la méthode map :

func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

La fonction map est générique (type T) et prend une fonction en paramètre et retourne une liste d’élément générique. Cette fonction que nous appellerons closure prend en paramètre un élément de la collection et retourne un objet de type générique.

Voici un petit cas d’utilisation très simple permettant de mettre en avant son utilité :

let nombres = [1, 2, 3, 4]
let doubles = nombres.map { $0 * 2 }

print(doubles) // Affiche [2, 4, 6, 8]

Dans cet example nous déclarons une variable nombres qui contient un liste d’entier. Puis en utilisant la méthode map sur nombres nous doublons ses chiffres pour les enregistrer dans la constante doubles.

Maintenant testons la fonction map avec un dictionnaire :

let inventaireFruits = ["Pomme": 4, "Poire": 7, "Pêche": 4]
let fruits           = inventaireFruits.map { (fruit, nombre) in fruit }

print(fruits) // ["Poire", "Pêche", "Pomme"]

Ici on a un inventaire de fruits avec leur intitulé et le nombre de fruit restant. La fonction map nous permet de transformer ce dictionnaire en liste avec la liste de tous les noms de fruit.

Reduce

La fonction reduce est certainement la plus difficile à appréhender au début. En voici sa signature pour commencer :

func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T

Cette méthode, comme la fonction map, est générique et prend 2 paramètres en entrée et retourne un élément générique. Le premier paramètre (initial) est de type générique et permet de définir les bases de la transformation, il sert d’accumulateur. Le deuxième paramètre est une closure qui prend en paramètre l’accumulateur et un élément de la collection et retourne un élément de type générique.

Plutôt qu’un long discours nous allons prendre un example pour illustrer son fonctionnement :

let phrase = "il était une fois dans la ville de fois un marchand de foie qui vendait du foie"
let mots   = phrase.componentsSeparatedByString(" ")

let occurrences = mots.reduce([String: Int]()) { (acc, mot) in
  var acc = acc

  acc[mot] = (acc[mot] ?? 0) + 1

  return acc
}

print(occurrences) // Affiche :
// ["marchand": 1, "du": 1, "qui": 1, "était": 1, "un": 1, "de": 2, "ville": 1, "il": 1, "la": 1, "vendait": 1, "dans": 1, "une": 1, "foie": 2, "fois": 2]

Dans cet example nous calculons le nombre d’occurrence de chaque mot. Pour ce faire nous définissons d’abord une phrase que l’on coupe en mot. Ensuite pour calculer le nombre d’occurrence de chacun des mots nous utilisons la méthode reduce sur la liste de mot. Nous définissons d’abord l’accumulateur en tant que dictionnaire de type [String: Int] vide puis une closure qui prend en paramètre l’accumulateur et un mot de la liste. Cette closure construit à partir des mots rencontrés une table d’occurence qu’il retourne à la fin pour servir d’accumulateur d’entré lors de la prochaine itération.

Pour déconstruire un peu tout ça voici la pile d’appel de la closure au fil des appels :

  1. acc = [:], mot = « il »
  2. acc = [« il »: 1], mot = « était »
  3. acc = [« il »: 1, « était »: 1], mot = « une »
  4. acc = [« il »: 1, « était »: 1, « une »: 1], mot = « fois »
  5. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 1], mot = « dans »
  6. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 1, « dans »: 1], mot = « la »
  7. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 1, « dans »: 1, « la »: 1], mot = « ville »
  8. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 1, « dans »: 1, « la »: 1, « ville »: 1], mot = « de »
  9. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 1, « dans »: 1, « la »: 1, « ville »: 1, « de »], mot = « fois »
  10. acc = [« il »: 1, « était »: 1, « une »: 1, « fois »: 2, « dans »: 1, « la »: 1, « ville »: 1, « de »], mot = « un »
  11. etc.

Filter

Comme son nom l’indique cette méthode permet de filtrer les éléments d’une collection. En voici sa signature :

func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]

La fonction filter prend en paramètre une closure et retourne une liste du même type que l’original. La closure elle prend en paramètre un élément de la liste et retourne un booléen permettant de savoir si oui ou non on garde l’élément dans la liste.

Imaginons une liste de nombre entier dont on souhaite garder uniquement ceux qui sont pairs :

let nombres      = [1, 2, 3, 4]
let nombresPairs = nombres.filter { $0 % 2 == 0 }

print(nombresPairs) // Affiche : [2, 4]

Dans la closure de la méthode filter nous vérifions que l’entier est pair. Si il est pair la valeur de l’opération est true, sinon false. Uniquement les opérations qui retournent true sont gardés.

De la même manière la méthode filter fonctionne aussi avec les dictionnaires ou tout autre collection :

let inventaireFruits = ["Pomme": 4, "Poire": 7, "Pêche": 4]
let fruits           = inventaireFruits.filter { (fruit, nombre) in nombre > 5 }

print(fruits) // ["Poire": 7]

Dans cet example on ne garde uniquement les fruits dont le nombre est supérieur à 5.

ForEach

La méthode forEach peut être vu comme une boucle for-in excepté qu’elle n’est pas conseillé quand il y a des effets de bord comme nous l’avions déjà expliqué dans cet article. Voici sa signature :

func forEach(@noescape body: (Self.Generator.Element) throws -> Void) rethrows

Cette méthode prend en paramètre une closure et ne retourne rien. La closure prend en paramètre un élément de la collection et ne retourne rien.

Comme la boucle for-in, la méthode forEach permet d’itérer au travers des éléments d’une collection :

let nombres = [1, 2, 3, 4]

nombres.forEach {
  print($0)
}

// Affiche :
// 1
// 2
// 3
// 4

FlatMap

flatMap apporte une fonctionnalité supplémentaire à son homologue map qui est de pouvoir aplatir une collection. Pour commencer voici sa signature qui est quasiment la même que celle de la fonction map :

func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]

La seule différence avec la méthode map est que la closure retourne un optionnel de type générique ce que vous allez voir permet de faire des choses très interessante.

Le premier cas d’utilisation est de pouvoir aplatir une liste de liste :

let arr     = [[1, 2], [3, 4]]
let flatten = arr.flatMap { $0 }

print(flatten) // Affiche : [1, 2, 3, 4]

Mais là où la fonction flatMap est intéressante c’est qu’il permet de faire le tri entre les éléments qui sont null et les autre comme dans cet example :

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

print(flatten) // Affiche : [1, 2, 3, 4, 5, 6]

Conclusion

Nous espérons que ces examples vous aurons aidé à comprendre un peu mieux le fonctionnement de ces différents fonctions. Si vous avez des questions, n’hésitez pas à laisser des commentaires.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...

Aucun commentaire

Annuler

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

  1. FONT Alain · 20 avril 2016

    Bonjour,
    Débutant en swift, j’essaye de me former grâce à des tutos comme le vôtre. Puis-je vous soumettre un problème que je n’arrive pas à résoudre correctement. Il s’agit de 2 dictionnaires REP1 et REP2 qui ont exactement les mêmes « keys » mais des « values » différentes. Je voudrais sortir pour chaque « key » la « value » de REP1 suivie de la « value » de REP2. Je ne sais pas si je suis assez clair. Je pourrai vous transmettre toutes les structures sur Xcode. Merci