En écoutant le cours sur les collections tiré de Principes de programmation fonctionnelle dans Scala , j’ai vu cet exemple:
scala> val s = "Hello World"
scala> s.flatMap(c => ("." + c)) // prepend each element with a period
res5: String = .H.e.l.l.o. .W.o.r.l.d
Ensuite, j’étais curieux de savoir pourquoi M. Odersky n’utilisait pas de map
ici. Mais, lorsque j'ai essayé la carte, j'ai obtenu un résultat différent de celui auquel je m'attendais.
scala> s.map(c => ("." + c))
res8: scala.collection.immutable.IndexedSeq[String] = Vector(.H, .e, .l, .l, .o,
". ", .W, .o, .r, .l,
Je m'attendais à ce que l'appel ci-dessus renvoie une chaîne, puisque je suis map
ing, c'est-à-dire que j'applique une fonction à chaque élément de la "séquence" puis que je retourne une nouvelle "séquence".
Cependant, je pourrais effectuer une map
plutôt que flatmap
pour un List[String]
:
scala> val sList = s.toList
sList: List[Char] = List(H, e, l, l, o, , W, o, r, l, d)
scala> sList.map(c => "." + c)
res9: List[String] = List(.H, .e, .l, .l, .o, ". ", .W, .o, .r, .l, .d)
Pourquoi un IndexedSeq[String]
est-il le type de retour de l'appel map
sur la chaîne?
La raison de ce comportement est que, pour appliquer "map" à une chaîne, Scala traite la chaîne comme une séquence de caractères (IndexedSeq[String]
). C’est ce que vous obtenez à la suite de l’appel de la carte, où l’opération est appliquée pour chaque élément de la séquence. Puisque Scala a traité la chaîne comme une séquence à appliquer map
, c'est ce que map
returns.
flatMap
invoque ensuite simplement flatten
sur cette séquence par la suite, qui la "reconvertit" ensuite en chaîne
Vous avez également une intéressante " collection d'exemples de Scala flatMap ", dont le premier illustre cette différence entre flatMap
et map
:
scala> val fruits = Seq("Apple", "banana", "orange")
fruits: Seq[Java.lang.String] = List(Apple, banana, orange)
scala> fruits.map(_.toUpperCase)
res0: Seq[Java.lang.String] = List(Apple, BANANA, ORANGE)
scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)
Toute une différence, non?
Étant donné queflatMap
traite uneString
comme une séquence deChar
, elle aplatit la liste de chaînes résultante en une séquence de caractères (Seq[Char]
).flatMap
est une combinaison demap
etflatten
, de sorte qu'il exécute d'abordmap
sur la séquence, puis exécuteflatten
, donnant le résultat affiché.Vous pouvez voir cela en exécutant map, puis vous aplatir:
scala> val mapResult = fruits.map(_.toUpperCase)
mapResult: Seq[String] = List(Apple, BANANA, ORANGE)
scala> val flattenResult = mapResult.flatten
flattenResult: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)
Votre fonction de carte c => ("." + c)
prend un caractère et retourne une chaîne. C'est comme prendre une liste et renvoyer une liste de listes. flatMap aplatit ce dos.
Si vous souhaitez renvoyer un caractère au lieu d'une chaîne, vous n'aurez pas besoin d'aplanir le résultat, par exemple. "abc".map(c => (c + 1).toChar)
renvoie "bcd".
Avec map
vous prenez une liste de caractères et la transformez en une liste de chaînes. C'est le résultat que vous voyez. Un map
ne change jamais la longueur d'une liste - la liste de chaînes contient autant d'éléments que la chaîne d'origine comporte de caractères.
Avec flatMap
, vous prenez une liste de caractères et vous la transformez en une liste de chaînes et puis vous regroupez ces chaînes en une seule chaîne . flatMap
est utile lorsque vous souhaitez transformer un élément d'une liste en plusieurs éléments sans créer de liste de listes. (Cela signifie bien entendu que la liste résultante peut avoir n'importe quelle longueur, y compris 0 - ce n'est pas possible avec map
à moins que vous ne commenciez avec la liste vide.)
Utilisez flatMap dans les situations où vous exécutez map suivi de flattern . La situation spécifique est la suivante:
• Vous utilisez map (ou un pour/yield expression) pour créer une nouvelle collection à partir d’une collection existante.
• La collection résultante est une liste de listes.
• Vous appelez aplatissez immédiatement après map (ou un pour/yield expression).
Lorsque vous vous trouvez dans cette situation, vous pouvez utiliser flatMap à la place.
Exemple: Ajouter tous les nombres entiers du sac
val bag = List("1", "2", "three", "4", "one hundred seventy five")
def toInt(in: String): Option[Int] = {
try {
Some(Integer.parseInt(in.trim))
} catch {
case e: Exception => None
}
}
Utiliser une méthode flatMap
> bag.flatMap(toInt).sum
Utilisation de la méthode map (3 étapes nécessaires)
bag.map(toInt) // List[Option[Int]] = List(Some(1), Some(2), None, Some(4), None)
bag.map(toInt).flatten //List[Int] = List(1, 2, 4)
bag.map(toInt).flatten.sum //Int = 7