首页 文章

Scala将Collection转换为按键映射的最佳方法是什么?

提问于
浏览
134

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

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

一种方法如下:

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

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

11 回答

  • 6

    您可以使用

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

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

  • 6

    您可以使用可变数量的元组构造Map . 因此,使用集合上的map方法将其转换为元组集合,然后使用:_ * trick将结果转换为变量参数 .

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

    除了@James Iry的解决方案之外,还可以使用折叠来实现此目的 . 我怀疑这个解决方案比tuple方法稍快(创建的垃圾对象更少):

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

    这可以通过如下折叠整个集合而不变地实现并且通过单次遍历来实现 .

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

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

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

  • 1

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

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

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

  • 9

    你想要实现的是有点不确定 .
    如果 c 中的两个或多个项目共享相同的 p 怎么办?哪个项目将映射到 Map 中的 p

    更准确的方式是在 p 和所有具有它的 c 物品之间产生一个 Map :

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

    这可以通过groupBy轻松实现:

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

    如果你仍然想要原始 Map ,你可以,例如,将 p 映射到它的第一个 t

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

    运作良好,非常直观

  • 198

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

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

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

    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 .

  • 16

    这对我有用:

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

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

  • 14

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

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

相关问题