【问题标题】:Why does this flatMap return a Map instead of a list?为什么这个 flatMap 返回的是 Map 而不是列表?
【发布时间】:2019-12-20 13:29:01
【问题描述】:

我正在尝试使用 flatMap 和 map 的组合将下面的 Map[T,List[T]] 转换为 List[T]

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 失败。

【问题讨论】:

  • 因为,对于 Map 有两种flatMap 的变体,如果函数的返回类型是另一个集合,一个会返回另一个 Map元组。有两种选择,一种是通过类型参数numMap.flatMap[(Int, Int)] { ... } 强制另一种重载,但是这个将返回 Iterable[(Int, Int)] 而不是 List。其他替代方法是使用 IteratornumMap.iterator.flatMap { ... }.toList.
  • 对一个单子的flatMap 操作应该返回另一个相同类型的单子,所以真正的问题是为什么Map.flatMap 返回另一个Map
  • @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)
  • @Benny 我不确定你到底在问什么,它不能是 Map 因为那不是 List元组,即具有两个元素的 IntsList。这就是为什么你得到一个 IntsIterable (不是一个 List - 你可能会说,你在那里看到一个 List,但那是 runtime class。变量的statictypeIterable。对于编译器,只有types 存在,您不能安全地将其用作List,只能用作Iterable
  • @LuisMiguelMejíaSuárez 谢谢!接受的答案澄清了地图的两种变体之间的区别。你是对的,我被运行时类List误导了,但是我不知道运行时类可以不同于静态类型。我在哪里可以找到有关此的更多信息?

标签: scala flatmap


【解决方案1】:

我可以通过一系列映射操作达到预期的结果

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)] 中,同时绕过中间集合的生成。

【讨论】:

  • 当然,这似乎是实现结果的更好方法。但是很抱歉我没有选择这个作为正确的解决方案,因为我的问题的目的是了解 flatMap 应该如何表现。
【解决方案2】:

根据scala docflatMap 函数有两个版本:一个返回新的可迭代集合,而另一个返回一个新的地图。 结果集合的类型由可迭代集合的静态类型引导,有时可能会导致意外结果。

简单来说,如果我们在flatMap 参数中提供的函数返回单个元素,我们将返回可迭代集合。另一方面,如果 flatMap 参数中的函数返回一个集合,我们将返回一个新的 Map。

例如这是您的输入变量:

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

【讨论】:

    猜你喜欢
    • 2019-11-23
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 2020-07-04
    相关资源
    最近更新 更多