Scala将Collection转换为Key-key的最佳方法?


165

如果我有一个集合c型的T,有一个属性pT(的类型P,比方说),什么是做一个最好的办法地图通过提取键

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

一种方法如下:

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

但是现在我需要一张可变的地图。有没有更好的方法可以做到这一点,使它排成一行,而最终得到一个不变的 Map?(显然,可以像在Java中那样将上述内容转换为简单的库实用程序,但我怀疑在Scala中没有必要)

Answers:


232

您可以使用

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

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


8
我仍然更喜欢我的建议,Traversable[K].mapTo( K => V)并且Traversable[V].mapBy( V => K)更好!
oxbow_lakes

7
请注意,这是二次运算,但此处给出的大多数其他变体也是如此。查看scala.collection.mutable.MapBuilder等的源代码,在我看来,对于每个元组,都会创建一个新的不可变映射,并在其中添加元组。
jcsahnwaldt恢复莫妮卡(Monica)2012年

30
在我的包含500,000个元素的列表的机器上,此Scala代码比简单的Java方法(创建具有适当大小的HashMap,遍历列表,将元素放入地图)慢约20倍。对于5,000个元素,Scala的速度慢了大约8倍。用Scala编写的循环方法大约比toMap变体快3倍,但仍然比Java慢2到7倍。
jcsahnwaldt恢复莫妮卡2012年

8
您能否向SO社区提供测试源?谢谢。
user573215 2013年

8
替换cc.iterator以避免创建中间集合。
ghik

21

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

5
我已经阅读了超过2周的有关Scala的文章,并通过示例进行了研究,而且从未见过这样的“:_ *”符号!非常感谢您的帮助
oxbow_lakes 2009年

仅作记录,我想知道为什么我们需要精确说明这是_的序列。map仍将转换返回一个元组列表。那么为什么_呢?我的意思是它的工作原理,但我想了解这里的类型归属
MaatDeamon

1
这比其他方法更有效吗?
2015年

16

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

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

我会尝试一下(我敢肯定它会起作用:-)。函数“(m,s)=> m(s)= s.length”是怎么回事?我已经看到了典型的foldLeft示例,其中包含一个求和和一个函数“ _ + _”;这更加令人困惑!该函数似乎假设我已经有一个元组(m,s),但我并没有真正得到它
oxbow_lakes 2009年

2
伙计,斯卡拉当时很奇怪!
missingfaktor'2

8
@Daniel我尝试您的代码,但出现以下错误:“值更新不是scala.collection.immutable.Map [String,Int]的成员”。请说明您的代码如何使用此代码?
mr.boyfox

1
似乎没有工作。对我来说,“应用程序都没有参数”
jayunit100

7
不变版本:list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }。请注意,如果要使用逗号来构建元组,则需要额外的一对括号:((s, s.length))
开尔文

11

通过如下折叠整个集合,可以一成不变地实现该功能。

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

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

这里要权衡的是代码的简单性与其效率。因此,对于大型集合,此方法可能比使用2个遍历实现(例如apply map和)更合适toMap


8

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

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

这避免了中介列表的创建,更多信息请参见Scala 2.8 breakOut


7

您要实现的目标有点不确定。
如果两个或多个项目c共享相同的内容p怎么办?哪个项目将映射到p地图中的那个项目?

更准确地看待这种情况的方式是,在p所有c具有此功能的项目之间绘制一个地图:

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

这可以通过groupBy轻松实现:

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

如果仍然需要原始地图,则可以例如映射pt具有原始地图的第一个地图:

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 }。这样,您可以在键入键[_]时进行诸如拼合地图的操作。
healsjnr

@healsjnr当然,对于任何地图都可以这样说。不过,这不是这里的核心问题。
Eyal Roth

1
您可以使用.mapValues(_.head)而不是地图。
lex82 '18 -4-3

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。


2
c map (_.getP) zip c

效果很好,非常直观


8
请添加更多详细信息。
Syeda Zunaira 2014年

2
对不起。但是,这是对以下问题的答案:“将集合变成按键映射的最佳方法?” 像本灵斯一样
约尔格Bächtiger

1
本没有提供任何解释?
shinzou

1
这将创建两个列表,并使用中的元素c作为键(某种)组合成一个“地图” 。注意“ map”,因为结果集合不是scala,Map而是创建了另一个元组列表/可迭代元组...但是出于OP的目的,效果是相同的,我不会轻视其简单性,但它不如foldLeft解决方案那么高效,也不是真正的答案“按键转换为收藏”
德克斯特·莱加皮


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))

另外,下面是这两个在Haskell中的外观:Map.fromList $ map (bar &&& id) cMap.fromList $ map (bar >>= (,)) c
missingfaktor

-1

这对我有用:

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

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


1
实际上,它可以按如下方式一成不变地实现: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }如上所述,该Map可以是不变的,因为将其添加到不可变的Map会返回带有附加条目的新的不可变的Map。该值通过折叠操作用作累加器。
RamV13


-3

如果从Json String(读取json文件)转换为Scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)

这个11岁的问题对JSON毫无疑问。您的答案不合适。
jwvh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.