我有一个键值对序列(String,Int),我想通过键将它们分组为一个值序列(即Seq[(String, Int)]) => Map[String, Iterable[Int]])
)。
显然,toMap
在这里没有用,并将groupBy
值维护为元组。我设法想出的最好的是:
val seq: Seq[( String, Int )]
// ...
seq.groupBy( _._1 ).mapValues( _.map( _._2 ) )
有更清洁的方法吗?
我有一个键值对序列(String,Int),我想通过键将它们分组为一个值序列(即Seq[(String, Int)]) => Map[String, Iterable[Int]])
)。
显然,toMap
在这里没有用,并将groupBy
值维护为元组。我设法想出的最好的是:
val seq: Seq[( String, Int )]
// ...
seq.groupBy( _._1 ).mapValues( _.map( _._2 ) )
有更清洁的方法吗?
这是一个toMultiMap
向可遍历对象添加方法的皮条客。它会解决你的问题吗?
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))
如果您添加以下隐式定义,它也将适用于类似集合的对象,例如String
s 和Array
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)
标准库中没有执行此操作的方法或数据结构,您的解决方案看起来尽可能简洁。如果您在多个地方使用它,您可能希望将其分解为实用方法
def groupTuples[A, B](seq: Seq[(A, B)]) =
seq groupBy (_._1) mapValues (_ map (_._2))
然后你显然只是用groupTuples(seq)
. 就 CPU 时钟周期而言,这可能不是最有效的,但我认为它也不是特别低效。
我在 9 个元组列表上对 Jean-Philippe 的解决方案做了一个粗略的基准测试,这稍微快了一点。两者的速度大约是将序列折叠到地图中的两倍(有效地重新实现groupBy
以提供您想要的输出)。
我不知道你是否认为它更干净:
seq.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
从 开始Scala 2.13
,大多数集合都提供了groupMap方法,该方法(顾名思义)与 a 等效(更有效),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
s 元素基于元组的第一部分 ( Map(2 -> List((2,c)), 1 -> List((1,a), (1,b)))
)
map
sList((1,a), (1,b))
通过取第二个元组部分 ( List(a, b)
) 对值 ( ) 进行分组。