为什么Scala地图上的.map.flatten和flatMap返回不同的结果?

时间:2019-01-17 07:12:50

标签: scala

我听说很多人说flatMapmap + flatten类似。例如,the answer

  

与众不同,对吧?
  因为flatMapString视为Char的序列,所以它将结果的字符串列表展平为一个字符序列(Seq[Char])。
  flatMapmapflatten的组合,因此它首先在序列上运行map,然后运行flatten给出结果。

但是我今天有一些代码问题。 mapflatMap的结果似乎有所不同。这是我的代码

object ListDemo {
  def main(args: Array[String]): Unit = {
    val map1 = Map("a" -> List(1 ->11,1->111), "b" -> List(2 -> 22, 2 ->222)).map(_._2).flatten
    val map2 = Map("a" -> List(1 ->11,1->111), "b" -> List(2 -> 22, 2 ->222)).flatMap(_._2)
    map1.foreach(println)
    println()
    map2.foreach(println)
  }
}

结果出乎意料。

(1,11)
(1,111)
(2,22)
(2,222)

(1,111)
(2,222)

为什么会这样?

3 个答案:

答案 0 :(得分:7)

在通过.map(f)Map上调用f时,对于某些K和V(不一定与原始{{ 1}}),结果将为(K, V)。否则,如果Map返回某些其他(非对)类型Map[K, V],则结果将为f。因此,如果函数返回一对则为T,否则返回Iterable[T]

在您的情况下,该函数返回Map,因此结果为Iterable-而不是Map。 List[(Int, Int)]然后将其变成Iterable[List[(Int, Int)]]

直接使用.flatten时,您将直接得到Iterable[(Int, Int)]对,因此结果将是flatMap-而不是(Int, Int)。而且由于Map[Int, Int]不允许重复的键,因此Iterable[(Int, Int)]包含的元素要少于Map

答案 1 :(得分:1)

您在这里有点误会,

因此,假设您有x: M[A]f: A = N[B]的任何Monad MN,则x.flatMap(f)应该与{{ 1}}。

但是这里拥有的是嵌套的monad x.map(f).flatten,而函数是map: M[N[A]],具有以下别名,

f: A => B

这种情况与上述将scala> type MapWithStringKey[A] = Map[String, A] // defined type alias MapWithStringKey scala> type TupleOfInt = (Int, Int) // defined type alias TupleOfInt scala> val map: MapWithStringKey[List[TupleOfInt]] = Map("a" -> List(1 ->11,1->111), "b" -> List(2 -> 22, 2 ->222)) // map: MapWithStringKey[List[TupleOfInt]] = Map(a -> List((1,11), (1,111)), b -> List((2,22), (2,222))) 连接到flatMapmap的标准定义完全不同。

现在,这只是非标准情况之一,您可以根据需要选择使用两种选择中的一种。并且,当我们添加flatten的特殊键唯一性属性(@ sepp2k已在答案中对此进行讨论)时,事情变得更加不可预测。

答案 2 :(得分:1)

TL; DR:两个调用的结果类型不同。对.map().flatten的调用返回一个Iterable[(Int, Int)],对.flatMap()的调用返回一个Map[Int, Int]。由于映射可能不会包含两次相同的键,因此每个键的第一项将被第二项覆盖。

Map视为Iterable[(Key,Value)]。调用.map时,必须为它提供一个返回元组(Key, Value)的函数(实际类型可能与原始的KeyValue不同)。

在您的示例中,Value恰好是List[(Int, Int)]。调用.map并返回原始Value的{​​{1}}时,您将得到一个Map,对Iterable[List[(Int, Int)]]的调用会变成一个{{ 1}},将“内部”列表串联在一起。如果要将那个转换为地图(通过调用.flatten),将会看到与Iterable[(Int, Int)]相同的结果。

现在,.toMap的不同之处在于它期望返回类型为flatMap,而不仅仅是flatMap。然后,它将返回的值用作新构造的Seq[(Key, Value)]中的条目。

在您的情况下,您的(Key, Value)的原始Map满足预期的收益类型,将原始Value转换为List[(Int, Int)]。由于映射不能包含具有相同键的两个条目,因此第二次出现该键将替换之前的出现。

要查看此行为,使用REPL(只需运行Map[(String, List[(Int, Int)])而不是编写主类将很有帮助,因此您可以看到中间值及其类型。

Map[(Int, Int)]