Почему этот flatMap возвращает карту вместо списка?

Я пытаюсь преобразовать приведенный ниже Map[T,List[T]] в List[T], используя комбинацию flatMap и map.

val numMap = Map(1 -> List(2) , 3 -> List(2,4))
val numPairs = numMap.flatMap{ case (n, nlist) => nlist.map (x => (x,n) ) }

Результат Map

res12: scala.collection.immutable.Map[Int,Int] = Map(2 -> 3, 4 -> 3)

вместо ожидаемого List

List((2,1), (2,3), (4,3))

Хотя я мог бы прийти к желаемому результату с помощью последовательности map операций

scala> numMap.map{ case (n, nlist) => nlist.map (x => (x,n) ).toList }.flatten
res20: scala.collection.immutable.Iterable[(Int, Int)] = List((2,1), (2,3), (4,3))

Я хочу понять, почему использование flatMap не удалось.


person Benny    schedule 14.08.2019    source источник
comment
Поскольку существует два варианта flatMap для Map, один из них вернет другой Map, если возвращаемый тип функции представляет собой другой набор кортежей. Есть две альтернативы, одна из которых заключается в принудительной перегрузке другой через параметры типа numMap.flatMap[(Int, Int)] { ... }, однако эта будет возвращать Iterable[(Int, Int)] вместо List. Другой альтернативой является использование итератора, numMap.iterator.flatMap { ... }.toList.   -  person Luis Miguel Mejía Suárez    schedule 14.08.2019
comment
Операция flatMap над монадой должна возвращать другую монаду того же типа, поэтому настоящий вопрос заключается в том, почему Map.flatMap не возвращает еще одну Map!   -  person Tim    schedule 14.08.2019
comment
@LuisMiguelMejíaSuárez, если так было всегда, почему следующее возвращает список scala> Map(2 -> 3, 4 -> 3).flatMap { case (k,v) => List(k,v) } res29: scala.collection.immutable.Iterable[Int] = List(2, 3, 4, 3)   -  person Benny    schedule 14.08.2019
comment
@Benny Я не уверен, что именно вы спрашиваете, это не может быть карта, потому что это не список кортежей, то есть Список из целых с двумя элементами. Вот почему вы возвращаете Iterable (не List) из Ints. Вы можете сказать, что вы см. там список, но это среда выполнения class. static type переменной Iterable. Для компилятора существует только types, вы не можете безопасно использовать его как List, только как Iterable.   -  person Luis Miguel Mejía Suárez    schedule 14.08.2019
comment
@LuisMiguelMejíaSuárez Спасибо! Принятый ответ прояснил разницу между двумя вариантами Карт. Вы правы, меня ввел в заблуждение рантайм-класс List, однако я не знал, что рантайм-класс может отличаться от статического типа. Где я могу найти дополнительную информацию об этом?   -  person Benny    schedule 17.08.2019
comment
@Benny это просто, потому что List является подтипом Iterable. Таким образом, реализация может выбрать его, не нарушая контракт. Вы можете сделать это в любом месте, это принцип подтипирования. Кроме того, вас может заинтересовать этот блог это объясняет разницу между временем компиляции типами и временем выполнения классами.   -  person Luis Miguel Mejía Suárez    schedule 17.08.2019


Ответы (2)


Согласно документу scala, существует две версии flatMap function: Одна возвращает новую итерируемую коллекцию, а другая возвращает новую карту. Тип результирующей коллекции определяется статическим типом итерируемой коллекции, что иногда может приводить к неожиданным результатам.

Проще говоря, если функция, которую мы предоставили в аргументе flatMap, возвращает один элемент, нам будет возвращена итерируемая коллекция. С другой стороны, если функция в аргументе flatMap возвращает коллекцию, нам будет возвращена новая Карта.

Например, вот ваша входная переменная:

scala> val numMap = Map(1 -> List(2) , 3 -> List(2,4))

Если мы вернем только один элемент из аргумента функции в flatMap, мы получим List

scala> numMap.flatMap{case(k,v) => v}
res0: List(2, 2, 4)

или (другой способ вернуть один элемент)

scala> numMap.flatMap{case(k,v) => v.map(e => e)}
res1: List(2, 2, 4)

Однако, если мы вернем коллекцию из функции в flatMap, мы получим Map. Здесь мы возвращаем кортеж (<element of list>, <key>)

scala> numMap.flatMap{case(k,v) => v.map(e => (e,k))}
res2: Map(2 -> 3, 4 -> 3)

Если мы хотим вернуть коллекцию из аргумента функции flatMap, мы можем использовать case class и получить List:

scala> case class MyCollection(a: Int, b: Int)
scala> numMap.flatMap{case(k,v) => v.map(e => MyCollection(e,k))}
res4: List(MyCollection(2,1), MyCollection(2,3), MyCollection(4,3))
person Ayush Vatsyayan    schedule 14.08.2019

Я мог бы прийти к намеченному результату с помощью последовательности операций над картой

scala> numMap.map{ case (n, nlist) => nlist.map(x => (x,n)).toList }.flatten
res20: scala.collection.immutable.Iterable[(Int, Int)] = List((2,1), (2,3), (4,3))

Вместо выполнения дополнительных преобразований для желаемого типа результата рассмотрите возможность использования breakOut. :

import scala.collection.breakOut

val numPairs: List[(Int, Int)] =
  numMap.flatMap{ case (n, nlist) => nlist.map((_, n)) }(breakOut)
// numPairs: List[(Int, Int)] = List((2,1), (2,3), (4,3))

С аннотацией типа numPairs: List[(Int, Int)] breakOut собирает полученные данные в List[(Int, Int)], минуя создание промежуточных коллекций.

person Leo C    schedule 14.08.2019
comment
Конечно, это кажется лучшим способом достижения результатов. Но извините, что я не выбрал это как правильное решение, поскольку целью моего вопроса было понять, как должен вести себя flatMap. - person Benny; 17.08.2019