Карта против FlatMap на String
Прослушивание лекции по коллекциям из Принципы функционального программирования в Scala, я увидел этот пример:
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
Тогда мне было любопытно, почему г-н Одерский не использовал здесь map
. Но, когда я попробовал карту, у меня был другой результат, чем я ожидал.
scala> s.map(c => ("." + c))
res8: scala.collection.immutable.IndexedSeq[String] = Vector(.H, .e, .l, .l, .o,
". ", .W, .o, .r, .l,
Я ожидал, что выше вызова вернуть String, так как я map
-ing, т.е. применяя функцию к каждому элементу в "последовательности", а затем возвращаю новую "последовательность".
Однако я мог бы выполнить map
, а не flatmap
для 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)
Почему был IndexedSeq[String]
возвращаемый тип вызова map
в строке?
Ответы
Ответ 1
Причиной такого поведения является то, что для применения "карты" к String Scala обрабатывает строку как последовательность символов (IndexedSeq[String]
). Это то, что вы получаете в результате вызова карты, где для каждого элемента указанной последовательности применяется операция. Поскольку Scala обрабатывает строку как последовательность для применения map
, это то, что возвращает map
.
flatMap
затем просто вызывает flatten
в этой последовательности после этого, которая затем "преобразует" его обратно в String
Ответ 2
Ваша функция карты c => ("." + c)
принимает char и возвращает строку. Это похоже на получение списка и возврат списка списков. flatMap выравнивает это назад.
Если вы вернете char вместо строки, вам не понадобится результат, сплющенный, например. "abc".map(c => (c + 1).toChar)
возвращает "bcd".
Ответ 3
У вас также есть интересная коллекция Scala примеров flatMap", первая из которых иллюстрирует разницу между flatMap
и 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)
В чем разница, верно?
Поскольку flatMap
рассматривает a String
как последовательность Char
, он выравнивает полученный список строк в последовательность символов (Seq[Char]
).
flatMap
представляет собой комбинацию map
и flatten
, поэтому она сначала запускает map
в последовательности, затем запускает flatten
, показывая результат.
Вы можете увидеть это, запустив карту, а затем сгладьте себя:
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)
Ответ 4
С map
вы берете список символов и превращаете его в список строк. Это результат, который вы видите. A map
никогда не изменяет длину списка - список строк имеет столько же элементов, сколько исходная строка содержит символы.
С помощью flatMap
вы берете список символов и превращаете его в список строк, а затем снова объединяете эти строки в одну строку. flatMap
полезно, когда вы хотите превратить один элемент в список в несколько элементов, не создавая список списков. (Это, конечно, также означает, что полученный список может иметь любую длину, включая 0 - это невозможно при map
, если вы не начинаете с пустого списка.)
Ответ 5
Используйте flatMap в ситуациях, когда вы запускаете карту, за которой следует фальтер. Конкретная ситуация такова:
• Вы используете карту (или выражение for/yield) для создания новой коллекции из существующей коллекции.
• Полученная коллекция представляет собой список списков.
• Вы вызываете сглаживание сразу после карты (или выражение для /yield ).
Когда вы в этой ситуации, вы можете использовать flatMap вместо.
Пример: добавьте целые числа из сумки
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
}
}
Использование метода flatMap
> bag.flatMap(toInt).sum
Использование метода карты (требуется 3 шага)
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