Scala 将 Collection 转化为按键映射的最佳方式?

如果我有一个类型为 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) }

但现在我需要一张 易变的地图。有没有更好的方法来做到这一点,使它在1行,我最终与 永恒不变映射?(显然,我可以像在 Java 中那样将上面的代码转换成一个简单的库实用程序,但我怀疑在 Scala 中没有这个必要)

173931 次浏览

您可以使用数量可变的元组构造一个 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)

除了@James Iry 的解决方案之外,还可以通过折叠来实现这一点。我怀疑这个解决方案比 tuple 方法稍微快一点(创建的垃圾对象更少) :

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

你可以用

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

但请注意,这需要两个遍历。

值得一提的是,这里有两种 毫无意义的方法:

scala> case class Foo(bar: Int)
defined class Foo


scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._


scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))


scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))


scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

另一种解决方案(可能不适用于所有类型)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

这样可以避免创建中间列表,更多信息请点击这里: Scala 2.8的突破

这可能不是将列表转换为映射的最有效方法,但它使调用代码更具可读性。我使用隐式转换将一个 地图方法添加到 List:

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 的 impiciConversion。

c map (_.getP) zip c

工作正常,直觉敏锐

这对我有用:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
(m, p) => m(p.id) = p; m
}

Map 必须是可变的,并且必须返回 Map,因为添加到可变的 Map 不会返回 Map。

你想要达到的目标有点模糊。
如果 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 }

这可以不变地实现,并通过折叠集合进行一次遍历,如下所示。

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

这个解决方案之所以能够工作,是因为添加到不可变 Map 中会返回一个带有附加条目的新的不可变 Map,并且这个值将作为折叠操作的累加器。

这里的折衷是代码的简单性与其效率。因此,对于大型集合,这种方法可能比使用2个遍历实现(如应用 maptoMap)更适合。

在集合中使用 map () ,然后使用 toMap

val map = list.map(e => (e, e.length)).toMap

使用 zip 和 toMap 如何?

myList.zip(myList.map(_.length)).toMap

Scala 2.13 +

而不是你可以用的“越狱”

c.map(t => (t.getP, t)).to(Map)

滚动到“查看”: https://www.scala-lang.org/blog/2017/02/28/collections-rework.html