web-dev-qa-db-fra.com

Carte versus FlatMap sur String

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 maping, 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?

22
Kevin Meredith

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 mapreturns.

flatMap invoque ensuite simplement flatten sur cette séquence par la suite, qui la "reconvertit" ensuite en chaîne

27
fresskoma

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é que flatMap traite une String comme une séquence de Char, elle aplatit la liste de chaînes résultante en une séquence de caractères (Seq[Char]).
flatMap est une combinaison de map et flatten, de sorte qu'il exécute d'abord map sur la séquence, puis exécute flatten, 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)
13
VonC

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".

7
ArtemGr

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.)

1
kqr

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
0