Карта flatten и flatmap не эквивалентная

Я думал, что Scala construct map(f).flatten эквивалентен flatMap(f). Но с этим примером это не так. Интересно, какова роль класса case в нем. Если я использую целые числа, то они эквивалентны. Но в моем случае я не могу.

case class CTest(v: Int)
val s = Set(Map(CTest(0) -> List(0, 3), CTest(1) -> List(0, 2)))
val possibilities = s flatMap { m =>
  val mapping = m flatMap {
    case (label, destNodes) => destNodes map {
      case nodes => (label, nodes) }
  }
  mapping
}
possibilities

Урожайность

Set((CTest(0),3), (CTest(1), 2))

тогда

case class CTest(v: Int)
val s = Set(Map(CTest(0) -> List(0, 3), CTest(1) -> List(0, 2)))
val possibilities = s flatMap { m =>
  val mapping = m map {
    case (label, destNodes) => destNodes map {
      case nodes => (label, nodes) }
  }
  mapping.flatten
}
possibilities

дает

Set((CTest(0),0), (CTest(0),3), (CTest(1),0), (CTest(1),2))

Любая идея, почему?

Ответы

Ответ 1

Взгляните на реализацию flatMap:

def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  def builder = bf(repr) // extracted to keep method size under 35 bytes, so that it can be JIT-inlined
  val b = builder
  for (x <- this) b ++= f(x).seq
  b.result
}

Результат flatMap зависит от исходного типа коллекции и типа результата функции f. В первом примере вы генерируете последовательность кортежей из карты, так что компилятор выбирает реализацию типа CanBuildFrom[Map[A, B], (C, D), Map[C, D]], которая предоставляет построитель для Map, который вызывает перезапись одних и тех же ключей.

Вы можете напрямую преобразовать карту в обычную итерабельную, что даст результат, который вы хотите:

case class CTest(v: Int)
val s = Set(Map(CTest(0) -> List(0, 3), CTest(1) -> List(0, 2)))
val possibilities = s flatMap { m =>
  val mapping = m.toIterable.flatMap {
    case (label, destNodes) => destNodes map {
      case nodes => (label, nodes) }
  }
  mapping
}
possibilities

Ответ 2

Это происходит из-за промежуточных структур данных.

Я возьму простую версию вашего примера.

val m = Map(CTest(0) -> List(0, 3), CTest(1) -> List(0, 2))

При использовании flatMap вы непосредственно создаете Map[CTest, Int]

scala> m flatMap {
 |     case (label, destNodes) => destNodes map {
 |       case nodes => (label, nodes) }
 |   }
res3: scala.collection.immutable.Map[CTest,Int] = Map(CTest(0) -> 3, CTest(1) -> 2)

Здесь, из-за уникальности клавиш Map, (CTest(0), 0) и (CTest(1), 0) будут удалены из результата. когда вы flatMap по множеству, вы получите Set из Tuples, которые были в Map.

В вашем втором примере вы отображаете и сглаживаете.

val mapping = m map {
 |     case (label, destNodes) => destNodes map {
 |       case nodes => (label, nodes) }
 |   }
mapping: scala.collection.immutable.Iterable[List[(CTest, Int)]] = List(List((CTest(0),0), (CTest(0),3)), List((CTest(1),0), (CTest(1),2)))

mapping.flatten
res4: scala.collection.immutable.Iterable[(CTest, Int)] = List((CTest(0),0), (CTest(0),3), (CTest(1),0), (CTest(1),2))

Нет никакой Map или другой уникальной сохраненной структуры данных, созданной в середине процесса. Поэтому значения не отбрасываются.