【问题标题】:Scala best way of turning a Collection into a Map-by-key?Scala 将 Collection 转换为 Map-by-key 的最佳方式?
【发布时间】:2010-10-15 01:11:07
【问题描述】:

如果我有一个c 类型为T 的集合,并且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 行,我最终得到一个 immutable 地图? (显然我可以将上面的内容变成一个简单的库实用程序,就像在 Java 中一样,但我怀疑在 Scala 中没有必要)

【问题讨论】:

    标签: scala map scala-collections


    【解决方案1】:

    你可以使用

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

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

    【讨论】:

    • 我还是更喜欢Traversable[K].mapTo( K => V) 和Traversable[V].mapBy( V => K) 中的建议更好!
    • 请注意,这是一个二次运算,但这里给出的大多数其他变体也是如此。查看 scala.collection.mutable.MapBuilder 等的源代码,在我看来,对于每个元组,都会创建一个新的不可变映射,将元组添加到该映射。
    • 在我的机器上,对于一个包含 500,000 个元素的列表,这个 Scala 代码比直接的 Java 方法慢了大约 20 倍(创建具有适当大小的 HashMap,遍历列表,将元素放入映射中)。对于 5,000 个元素,Scala 慢了大约 8 倍。用 Scala 编写的循环方法比 toMap 变体快大约 3 倍,但仍然比 Java 慢 2 到 7 倍。
    • 能否将测试源提供给 SO 社区?谢谢。
    • 将c 替换为c.iterator 以避免创建中间集合。
    【解决方案2】:

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

    【讨论】:

    • 我已经阅读有关 Scala 的文章超过 2 周并通过示例进行工作,但我从未见过这种“:_ *”符号!非常感谢您的帮助
    • 只是为了记录,我想知道为什么我们需要精确说明这是一个带有 _ 的序列。 map 仍然在此处转换返回元组列表。那么为什么是 _ 呢?我的意思是它有效,但我想了解这里的类型归属
    • 这比其他方法效率高吗?
    【解决方案3】:

    除了@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 示例,其中包含一个 sum 和一个函数“_ + _”;这更令人困惑!该函数似乎假设我已经有一个元组 (m,s),但我并没有真正得到
    • 伙计,当时的 Scala 很奇怪!
    • @Daniel 我尝试了您的代码,但出现以下错误:“值更新不是 scala.collection.immutable.Map[String,Int] 的成员”。请解释您的代码如何使用此代码?
    • 似乎不起作用。对我来说“应用程序不接受参数”
    • 不可变版本:list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }。请注意,如果要使用逗号构建元组,则需要额外的一对括号:((s, s.length))。
    【解决方案4】:

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

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

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

    这里的权衡是代码的简单性与其效率。所以,对于大型集合,这种方法可能比使用map和toMap等2种遍历实现更合适。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        您想要实现的目标有点不确定。
        如果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 }
        

        【讨论】:

        • 对此的一个方便的调整是使用collect 而不是map。例如:c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }。这样,当您键入 Option[_] 时,您可以执行诸如展平地图之类的操作。
        • @healsjnr 当然,这可以用于任何地图。不过,这不是这里的核心问题。
        • 您可以使用.mapValues(_.head) 代替地图。
        【解决方案7】:
        c map (_.getP) zip c
        

        效果很好,非常直观

        【讨论】:

        • 请添加更多详细信息。
        • 对不起。但是,这是对“Scala 将 Collection 转换为 Map-by-key 的最佳方式?”问题的答案。就像 Ben Lings 一样。
        • Ben 没有提供任何解释?
        • 这会创建两个列表并使用c 中的元素作为键(有点)组合成一个“地图”。请注意“map”,因为生成的集合不是 scala Map,而是创建另一个元组列表/可迭代...但是对于 OP 的目的,效果是相同的,我不会低估简单性,但它不如 @ 高效987654324@ 解决方案,也不是“将集合转换为按键映射”问题的真正答案
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          使用 zip 和 toMap 怎么样?

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

          【讨论】:

            【解决方案10】:

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

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

            【讨论】:

            • 另外,fwiw,这就是这两个在 Haskell 中的样子:Map.fromList $ map (bar &&& id) c,Map.fromList $ map (bar >>= (,)) c。
            【解决方案11】:

            Scala 2.13+

            你可以用“breakOut”代替

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

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

            【讨论】:

              【解决方案12】:

              这对我有用:

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

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

              【讨论】:

              • 实际上,它可以不可变地实现如下:val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) } Map 可以是不可变的,如上所示,因为添加到不可变 Map 会返回一个带有附加条目的新不可变 Map。该值通过折叠操作作为累加器。
              【解决方案13】:

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

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

              【讨论】:

              • 这与 7 年前提交并接受的答案有何不同?
              猜你喜欢
              • 1970-01-01
              • 2010-11-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-08-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多