为什么带有 yield 的 for 循环会累积到 Map 而不是 List 中?

Why for-loop with yield accumulates into a Map instead of a List?

我有以下代码:

val dummy = Map(1 -> Map(2 -> 3.0,
                         4 -> 5.0),
                6 -> Map(7 -> 8.0))

val thisIsList = for (x <- dummy; y <- x._2.keys) yield s"(${x._1}, ${y})"
println(thisIsList)  // List((1, 2), (1, 4), (6, 7))

val thisIsMap = for (x <- dummy; y <- x._2.keys) yield new Tuple2(x._1, y)
println(thisIsMap)   // Map(1 -> 4, 6 -> 7) - this is not what I want    

我希望第二条语句生成一个元组列表,但它 return 是一个映射。 我在这里 scala: yield a sequence of tuples instead of map 找到了关于为什么 Map 是 returned 的解释,但我仍在努力寻找一种优雅的方法来 return 元组列表,而不是在这种情况下。

这是因为 for 理解语法如何被编译器转换为一系列方法调用。 mapflatMapwithFilterfor 理解排列的目标。这是非常强大和通用的,因为它允许语法适用于任意类型。还有更多,例如 CanBuildFrom 隐式,但本质上将 Map 映射到 Iterable[Tuple[A, B]] 会产生 Map[A, B]The signature is actually overloaded for Map to provide this behavior

具体来说,给出下面的原始代码

val thisIsMap = for (x <- dummy; y <- x._2.keys) yield new Tuple2(x._1, y)
println(thisIsMap)   // Map(1 -> 4, 6 -> 7) - this is not what I want

翻译大概是这样

val thisIsMap = dummy.flatMap { x =>
  x._2.keys.map { y =>
    (x._1, y)
  }
}

看到这个fiddle

为了得到想要的列表,我们可以这样写

val thisIsMap = (for (x <- dummy; y <- x._2.keys) yield (x._1, y)).toList

然而,如果我们考虑到我们对 for 理解的了解,我们可以更优雅地写成

val thisIsMap = for (x <- dummy.toList; y <- x._2.keys) yield (x._1, y)

在上面,我们通过推断 forList 的理解将产生 List.[=36= 来利用混淆原始代码的行为。 ]

但是,请注意将源映射转换为 List 与将生成的映射转换为List领悟后

如果我们在源 (dummy) 上调用 toList,我们会得到 List((1,2), (1,4), (6,7)),而如果我们在结果上调用它,我们会得到 List((1,4), (6,7)),这是不言而喻的所以谨慎选择的原因。

尝试

dummy
  .view
  .mapValues(_.keys.toList)
  .flatMap { case (key: Int, values: List[Int]) => values.map((key, _)) }
  .toList

输出

res0: List[(Int, Int)] = List((1,2), (1,4), (6,7)

完成答案后,post 将在此处对我自己的问题进行 TLDR 摘要。

for 理解循环 return 返回的数据结构类型预计与 for 循环开始迭代。 IE。如果它开始遍历 Map - 期望 Map 是最终结果。

val thisIsList = for (x <- dummy; y <- x._2.keys) yield s"(${x._1}, ${y})"
println(thisIsList)  // List((1, 2), (1, 4), (6, 7))

在问题的这个例子中,for 循环开始遍历 Map,但 return 是一个列表。这是因为 yield 不是 return 可以转换为 Map 的类型。但是它可以转换成List,所以Scala就可以做到。

val thisIsMap = for (x <- dummy; y <- x._2.keys) yield new Tuple2(x._1, y)
println(thisIsMap)   // Map(1 -> 4, 6 -> 7) - this is not what I want 

虽然在这个例子中,一切都按预期进行,但是由于生成的 Map 不能有重复的键,元组 (1,2) 会被元组 (​​1,4) 覆盖。 IE。该地图仅包含 2 个元素。