【问题标题】:Transforming a map in Scala在 Scala 中转换地图
【发布时间】:2026-02-07 01:45:01
【问题描述】:

我想在 Scala 中转换 String -> List[String] 类型的映射,使得每个 (k, (List v1, v2, ... vn)) 键值对都指向 {(k:v1, k), (k:v2, k), ... (k:vn, k)} 对。

比如我想改造

scala.collection.immutable.Map[String,List[String]] = Map(A -> List(a1, a2), B -> List(b1))

scala.collection.immutable.Map[String,List[String]] = Map(A:a1 -> A, A:a2 -> A, B:b1 -> B)

我能够使用 [this answer][1] 实现我的目标:

scala> val schema = Map("A" -> List("a1", "a2"),  "B" -> List("b1"))
schema: scala.collection.immutable.Map[String,List[String]] = Map(A -> List(a1, a2), B -> List(b1))

scala>  schema flatten {case(k, vs) => vs.map((_, k))}
res1: scala.collection.immutable.Iterable[(String, String)] = List((a1,A), (a2,A), (b1,B))

当我尝试在每个值前面加上原始键和冒号时,我得到一个错误:

scala> schema flatten {case(k, vs) => vs.map((k.concat(":").concat(_), k))}
<console>:13: error: type mismatch;
 found   : (String => String, String)
 required: String => ?
       schema flatten {case(k, vs) => vs.map((k.concat(":").concat(_), k))}

【问题讨论】:

  • flatten?从什么时候开始有人使用flatten 而不是flatMap
  • @TimothyJones flatten 没有任何问题。但是滥用隐式函数参数使其表现得好像flatMap 真的很丑陋。应该记住,这个隐式函数参数的存在只是为了通过将类型参数A 转换为Iterable[B] 对于某些B 来使类型检查器满意。在无需隐式转换函数即可表达此约束的严格类型编程语言中,或在无类型语言中,flatten 甚至根本不接受任何参数。 flatten 应该实现基本的单子“乘法”。它不需要任何参数。
  • 谢谢!我还注意到在这种情况下它会导致错误的返回类型。

标签: scala anonymous-function scala-collections


【解决方案1】:
  1. (k.concat(":").concat(k), k) 中没有 _ 和变量,所以它不是函数字面量,但 map 的参数必须是函数。
  2. 无论如何,您都不想将kk 连接

试试这个:

schema.flatMap { case (k, vs) => vs.map(v => (k + ":" + v, k)) }

或者更简洁:

for ((k, vs) <- schema; v <- vs) yield (k + ":" + v, k)

编辑

对于每个k,表达式k.concat(":").concat(_) 是一个接受字符串s 并计算k + ":" + s 的函数。

因此,(k.concat(":").concat(_), k) 是一个元组

  1. 一个函数x =&gt; k.concat(":").concat(x)
  2. k

所以它的类型为(String =&gt; String, String)。这与String =&gt; (String, String) 不同,这仍然不是您想要的。

【讨论】:

  • 谢谢。我确实在我的帖子中犯了一个错误,我根据您的回答进行了修复。当我切换到(k.concat(":").concat(_), k) 时,我仍然收到错误,尽管是不同的。
  • 安德烈,非常感谢您回答我的原始问题和编辑问题的两个部分。我很感激理解我做错了什么,如何解决它,以及最优雅的解决方案。
【解决方案2】:

错误消息告诉您.map 采用String =&gt; ? 形式的函数,但您传递给它的是(String, String) 的元组。

(k.concat(":").concat(k), k) 不是一个函数 - 我认为您并不是要在 concat 中输入两次 k

要创建地图,您需要一个带有签名String =&gt; (String, String)的函数

  • vs.map((k.concat(":").concat(k),k)) 错误地将 (String, String) 传递给映射
  • vs.map((k.concat(":").concat(_),k)) 错误地将 (String =&gt; String, String) 传递给映射。
  • vs.map(v =&gt; (k.concat(":").concat(v), k)) 正确匹配 String =&gt; (String, String)

我们可以得到:

schema flatten {case(k, vs) => vs.map(v => (k.concat(":").concat(v), k)) }
 //  List((A:a1,A), (A:a2,A), (B:b1,B)) 

您可以在here 看到此代码。

但是,正如 Andrey 出色的评论所指出的,将 flatten 与隐式函数一起使用并不是一个好习惯。此外,您需要Map 而不是List。由于这两个原因,我认为你应该使用flatMap 而不是flatten

schema flatMap {case(k, vs) => vs.map(v => (k.concat(":").concat(v), k)) };
// Map(A:a1 -> A, A:a2 -> A, B:b1 -> B)

如果您想试用,请参阅here

【讨论】:

  • 谢谢。正如你所猜测的,我确实在我的帖子中犯了一个错误。查看对 Andrey 的更新问题和评论。
  • 这个答案与安德烈的不同——对你有用吗?