172

如果我有一个c类型的集合T并且有一个属性p(例如Ttype P),那么执行map-by-extracting-key的最佳方法是什么?

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 中没有必要)

4

13 回答 13

248

您可以使用

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

但请注意,这需要 2 次遍历。

于 2010-07-14T18:56:24.533 回答
22

您可以使用可变数量的元组构造一个 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)
于 2009-03-23T21:11:37.087 回答
20

除了@James Iry 的解决方案之外,还可以使用折叠来完成此操作。我怀疑这个解决方案比元组方法稍快(创建的垃圾对象更少):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
于 2009-03-24T18:39:42.063 回答
12

这可以通过按如下方式折叠集合来实现不变,并且只需一次遍历即可。

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

该解决方案之所以有效,是因为添加到不可变 Map 会返回一个带有附加条目的新不可变 Map,并且该值通过折叠操作用作累加器。

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

于 2016-12-13T17:49:15.387 回答
9

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

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

这避免了中间列表的创建,更多信息在这里: Scala 2.8 breakOut

于 2013-10-28T19:24:04.243 回答
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 }
于 2015-12-03T15:58:06.873 回答
3
c map (_.getP) zip c

效果很好,非常直观

于 2014-12-04T10:37:56.940 回答
2

这可能不是将列表转换为映射的最有效方法,但它使调用代码更具可读性。我使用隐式转换将mapBy方法添加到 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的implicitConversions。

于 2014-07-27T06:22:49.880 回答
2

使用 zip 和 toMap 怎么样?

myList.zip(myList.map(_.length)).toMap
于 2019-07-03T20:25:23.823 回答
1

对于它的价值,这里有两种毫无意义的方法:

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))
于 2012-02-04T10:07:23.747 回答
1

斯卡拉 2.13+

而不是“breakOut”,你可以使用

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

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

于 2020-09-01T14:14:18.250 回答
-1

这对我有用:

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

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

于 2015-02-25T06:21:46.087 回答
-2

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

val map = list.map(e => (e, e.length)).toMap
于 2017-12-24T11:40:05.233 回答