Scala将集合转换为按键映射的最佳方法是什么?

180

如果我有一个类型为 T 的集合 c,并且在 T 上有一个属性 p(比如类型为 P),那么做一个 通过提取键进行映射 的最佳方法是什么?

val c: Collection[T]
val m: Map[P, T]

一种方法如下所述:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

但现在我需要一个可变的映射。有没有更好的方法可以在一行内完成,并且最终得到一个不可变的映射?(显然,我可以将上述内容转化为简单的库实用程序,就像在Java中一样,但我怀疑在Scala中没有这个必要)

13个回答

258

您可以使用

c map (t => t.getP -> t) toMap

但需要注意的是,这需要进行两次遍历。


8
我仍然认为我的建议在Traversable[K].mapTo(K => V)和Traversable[V].mapBy(V => K)方面更好! - oxbow_lakes
7
请注意,这是一个二次操作,但大多数其他给出的变量也是如此。查看scala.collection.mutable.MapBuilder等源代码后,我发现对于每个元组,都会创建一个新的不可变映射,并将元组添加到其中。 - jcsahnwaldt Reinstate Monica
32
在我的电脑上,对于一个包含 500,000 个元素的列表,这段 Scala 代码比直接用 Java 方法处理(创建具有适当大小的 HashMap,循环遍历列表,将元素放入 Map 中)慢大约 20 倍。对于 5,000 个元素,Scala 大约比 Java 慢了 8 倍。使用 Scala 编写的循环方法大约比 toMap 变体快 3 倍,但仍比 Java 慢 2 到 7 倍。 - jcsahnwaldt Reinstate Monica
8
请问你能否向SO社区提供测试源代码?谢谢。 - user573215
8
c 替换为 c.iterator 可以避免创建中间集合。 - ghik
显示剩余5条评论

24

您可以使用可变数量的元组构造Map。 因此,使用集合上的map方法将其转换为包含元组的集合,然后使用“:_*”技巧将结果转换为可变参数。

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)

6
我已经阅读了超过两周的Scala相关资料,并且一直在进行例子实践,但我从未见过这种 ": _ *" 的符号!非常感谢您的帮助。 - oxbow_lakes
仅供记录,我想知道为什么我们需要明确这是一个使用 _* 的序列,因为 map 仍然会在这里返回元组列表。那么为什么要 _*?我的意思是它可以工作,但我想了解此处的类型注释。 - MaatDeamon
1
这个方法比其他方法更有效吗? - Jus12

20

除了 @James Iry 的解决方案外,也可以使用 fold 来完成此操作。我怀疑这种方法比元组方法稍微快一些(创建的垃圾对象较少):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }

这是Scala中用于更新的语法糖:将函数应用程序放在“=”运算符左侧的赋值f(args) = e被解释为f.update(args, e),即调用由f定义的更新函数。[Scala语言规范第2.7版,6.15赋值] - Palimondo
2
Scala 那时候真是奇怪! - missingfaktor
9
@Daniel,我尝试了你的代码,但出现了以下错误:“值更新不是scala.collection.immutable.Map [String,Int]的成员”。请解释一下你的代码如何运作? - SBotirov
1
似乎对我也不起作用:“应用程序不接受参数”。 - jayunit100
7
不可变版本:list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }。请注意,如果您想使用逗号来构建元组,则需要额外的括号:((s, s.length)) - Kelvin
显示剩余4条评论

14
这可以通过对集合进行折叠来实现不可变性和单次遍历。
val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

该解决方案可行是因为向不可变 Map 中添加元素会返回一个新的不可变 Map,并且该值作为累加器通过折叠操作。这种方法的权衡是代码的简单性与效率之间的平衡。因此,对于大型集合,使用此方法可能比使用 2 个遍历实现(例如应用 map 和 toMap)更适合。

9
另一种解决方案(可能不适用于所有类型)
import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

这可以避免创建中间列表,更多信息请看: Scala 2.8 breakOut


8
你尝试实现的目标有些不明确。
如果 c 中有两个或多个项目共享相同的p,那么哪个项目将映射到映射中的p

更准确的方法是生成p和所有具有它的c项之间的映射:

val m: Map[P, Collection[T]]

这可以很容易地通过groupBy实现:

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

如果您仍然需要原始地图,例如,您可以将 p 映射到具有该标签的第一个 t
val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }

1
其中一个方便的调整是使用collect代替map。例如:c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }。这样,当您的键是Option[_]时,您可以执行诸如扁平化映射之类的操作。 - healsjnr
@healsjnr 当然,这也适用于任何地图。不过,这并不是核心问题。 - Eyal Roth
1
你可以使用 .mapValues(_.head) 替代 map。 - lex82

6

2

这可能不是将列表转换为映射的最有效方法,但它可以使调用代码更易读。我使用隐式转换将 mapBy 方法添加到列表中:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

调用代码示例:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

注意,由于隐式转换的存在,调用方代码需要导入Scala的implicitConversions。

2

使用zip和toMap怎么样?

最初的回答
myList.zip(myList.map(_.length)).toMap

2
c map (_.getP) zip c

表现良好且非常直观。


9
请添加更多细节。 - Syeda Zunaira
2
很抱歉,但这确实是对于“Scala最佳方式将集合转换为键值Map?”这个问题的答案,就像Ben Lings所说的一样。 - Jörg Bächtiger
1
那么Ben没有提供任何解释吗? - shinzou
1
这将创建两个列表, 并使用c中的元素作为键(类似于排序)合并为“映射”。请注意“映射”, 因为结果集不是Scala Map, 而是创建了另一个元组的列表/可迭代对象...但对于OP的目的来说效果是相同的。我不会否定其简单性,但它不像foldLeft解决方案那样高效,也不是将集合转换为map-by-key的真正答案。 - Dexter Legaspi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接