Scala 将 Collection 变成 Map-by-key 的最佳方式?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/674639/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 16:19:33  来源:igfitidea点击:

Scala best way of turning a Collection into a Map-by-key?

scalamapscala-collections

提问by oxbow_lakes

If I have a collection cof type Tand there is a property pon T(of type P, say), what is the best way to do a map-by-extracting-key?

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

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

One way is the following:

一种方法如下:

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

But now I need a mutablemap. Is there a better way of doing this so that it's in 1 line and I end up with an immutableMap? (Obviously I could turn the above into a simple library utility, as I would in Java, but I suspect that in Scala there is no need)

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

回答by Ben Lings

You can use

您可以使用

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

but be aware that this needs 2 traversals.

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

回答by James Iry

You can construct a Map with a variable number of tuples. So use the map method on the collection to convert it into a collection of tuples and then use the : _* trick to convert the result into a variable argument.

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

回答by Daniel Spiewak

In addition to @James Iry's solution, it is also possible to accomplish this using a fold. I suspect that this solution is slightly faster than the tuple method (fewer garbage objects are created):

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

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

回答by RamV13

This can be implemented immutably and with a single traversal by folding through the collection as follows.

这可以通过如下折叠集合来不变地实现,并且可以通过单次遍历来实现。

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

The solution works because adding to an immutable Map returns a new immutable Map with the additional entry and this value serves as the accumulator through the fold operation.

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

The tradeoff here is the simplicity of the code versus its efficiency. So, for large collections, this approach may be more suitable than using 2 traversal implementations such as applying mapand toMap.

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

回答by Somatik

Another solution (might not work for all types)

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

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

this avoids the creation of the intermediary list, more info here: Scala 2.8 breakOut

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

回答by Eyal Roth

What you're trying to achieve is a bit undefined.
What if two or more items in cshare the same p? Which item will be mapped to that pin the map?

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

The more accurate way of looking at this is yielding a map between pand all citems that have it:

更准确的查看方式是在p所有c项目之间生成一张地图:

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

This could be easily achieved with groupBy:

这可以通过groupBy轻松实现:

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

If you still want the original map, you can, for instance, map pto the first tthat has it:

如果您仍然想要原始地图,例如,您可以映射p到第一个t拥有它的地图:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }

回答by AwesomeBobX64

How about using zip and toMap?

使用 zip 和 toMap 怎么样?

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

回答by Erez

This is probably not the most efficient way to turn a list to map, but it makes the calling code more readable. I used implicit conversions to add a mapBymethod to List:

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

Calling code example:

调用代码示例:

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

Note that because of the implicit conversion, the caller code needs to import scala's implicitConversions.

注意因为隐式转换,调用者代码需要导入scala的implicitConversions。

回答by J?rg B?chtiger

c map (_.getP) zip c

Works well and is very intuitiv

效果很好,非常直观

回答by missingfaktor

For what it's worth, here are two pointlessways of doing it:

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

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