更清洁的元组组

时间:2012-05-28 11:16:16

标签: scala scala-collections

我有一系列键值对(String,Int),我想按键将它们分组为一系列值(即Seq[(String, Int)]) => Map[String, Iterable[Int]]))。

显然,toMap在这里没用,groupBy将值维护为元组。我设法得到的最好的是:

val seq: Seq[( String, Int )]
// ...
seq.groupBy( _._1 ).mapValues( _.map( _._2 ) )

有更清洁的方法吗?

4 个答案:

答案 0 :(得分:19)

这是一个为可遍历添加toMultiMap方法的pimp。它会解决你的问题吗?

import collection._
import mutable.Builder
import generic.CanBuildFrom

class TraversableOnceExt[CC, A](coll: CC, asTraversable: CC => TraversableOnce[A]) {

  def toMultiMap[T, U, That](implicit ev: A <:< (T, U), cbf: CanBuildFrom[CC, U, That]): immutable.Map[T, That] =
    toMultiMapBy(ev)

  def toMultiMapBy[T, U, That](f: A => (T, U))(implicit cbf: CanBuildFrom[CC, U, That]): immutable.Map[T, That] = {
    val mutMap = mutable.Map.empty[T, mutable.Builder[U, That]]
    for (x <- asTraversable(coll)) {
      val (key, value) = f(x)
      val builder = mutMap.getOrElseUpdate(key, cbf(coll))
      builder += value
    }
    val mapBuilder = immutable.Map.newBuilder[T, That]
    for ((k, v) <- mutMap)
      mapBuilder += ((k, v.result))
    mapBuilder.result
  }
}

implicit def commomExtendTraversable[A, C[A] <: TraversableOnce[A]](coll: C[A]): TraversableOnceExt[C[A], A] =
  new TraversableOnceExt[C[A], A](coll, identity)

可以这样使用:

val map = List(1 -> 'a', 1 -> 'à', 2 -> 'b').toMultiMap
println(map)  // Map(1 -> List(a, à), 2 -> List(b))

val byFirstLetter = Set("abc", "aeiou", "cdef").toMultiMapBy(elem => (elem.head, elem))
println(byFirstLetter) // Map(c -> Set(cdef), a -> Set(abc, aeiou))

如果添加以下隐式defs,它也可以用于类似集合的对象,例如StringArray s:

implicit def commomExtendStringTraversable(string: String): TraversableOnceExt[String, Char] =
  new TraversableOnceExt[String, Char](string, implicitly)

implicit def commomExtendArrayTraversable[A](array: Array[A]): TraversableOnceExt[Array[A], A] =
  new TraversableOnceExt[Array[A], A](array, implicitly)

然后:

val withArrays = Array(1 -> 'a', 1 -> 'à', 2 -> 'b').toMultiMap
println(withArrays) // Map(1 -> [C@377653ae, 2 -> [C@396fe0f4)

val byLowercaseCode = "Mama".toMultiMapBy(c => (c.toLower.toInt, c))
println(byLowercaseCode) // Map(97 -> aa, 109 -> Mm)

答案 1 :(得分:12)

标准库中没有方法或数据结构来执行此操作,您的解决方案看起来就像您将获得的简洁。如果您在多个地方使用此功能,您可能希望将其分解为实用程序方法

def groupTuples[A,B](seq: Seq[(A,B)]) = 
  seq groupBy (_._1) mapValues (_ map (_._2))

然后你显然只是用groupTuples(seq)打电话。就CPU时钟周期而言,这可能不是最有效的,但我认为它也不是特别低效。

我在9个元组列表中对Jean-Philippe的解决方案进行了粗略的基准测试,这个速度略快一些。两者的速度都是将序列折叠到地图中的两倍(有效地重新实现groupBy以提供您想要的输出)。

答案 2 :(得分:8)

我不知道你是否认为它更干净:

seq.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}

答案 3 :(得分:0)

for开始,大多数集合都提供了groupMap方法,该方法(顾名思义)与Scala 2.13后跟groupBy等效(效率更高) :

mapValues

此:

  • List(1 -> 'a', 1 -> 'b', 2 -> 'c').groupMap(_._1)(_._2) // Map[Int,List[Char]] = Map(2 -> List(c), 1 -> List(a, b)) 的元素基于元组(group)的第一部分

  • Map(2 -> List((2,c)), 1 -> List((1,a), (1,b)))的分组值(map)的第二元组部分(List((1,a), (1,b)))。