我需要一个按元组列表分组和平均的方法。
这是我目前的实施:
// (a, 1), (a, 2), (b, 3) -> (a, 1.5), (b, 3)
def groupByAndAvg[T, U](ts: Iterable[(T, U)])(implicit num: Numeric[U]): Iterable[(T, Double)] = {
ts.groupBy(_._1)
.mapValues { _.unzip._2 }
.mapValues { xs => num.toDouble(xs.sum) / xs.size }
}
在性能或简单方面有没有更好的方法?
答案 0 :(得分:6)
unzip
没有理由产生两个集合。只做.map(_._2)
就可以得到你所需要的东西。map
而不是mapValues
,因为mapValues
只会创建视图新地图的含义,意味着每次访问都会重新计算平均值。Iterable
,因为您失去了groupBy
为您提供Map
的事实。所以也许你想要这个:
def groupByAndAvg[T, U](ts: Iterable[(T, U)])(implicit num: Numeric[U]) = {
ts.groupBy(_._1).map {
case (key, pairs) =>
val values = pairs.map(_._2)
key -> (num.toDouble(values.sum) / values.size)
}
}
groupByAndAvg(Vector(("a", 1), ("a", 2), ("b", 3)))
// res0: scala.collection.immutable.Map[String,Double] = Map(b -> 3.0, a -> 1.5)
如果您经常这样做,您可以定义自己的收集方法。在这里,我定义groupByKey
,其中Traverable[(K,V)]
并返回Map[K,Traverable[V]]
和avg
:
import scala.collection.TraversableLike
import scala.collection.generic.CanBuildFrom
import scala.collection.immutable
import scala.collection.mutable
implicit class EnrichedWithGroupByKey[K, V, Repr](val self: TraversableLike[(K, V), Repr]) extends AnyVal {
def groupByKey[That](implicit bf: CanBuildFrom[Repr, V, That]): Map[K, That] = {
val m = mutable.Map.empty[K, mutable.Builder[V, That]]
for ((key, value) <- self) {
val bldr = m.getOrElseUpdate(key, bf(self.asInstanceOf[Repr]))
bldr += value
}
val b = immutable.Map.newBuilder[K, That]
for ((k, v) <- m)
b += (k -> v.result)
b.result
}
}
implicit class EnrichedWithAvg[A](val self: Traversable[A])(implicit num: Numeric[A]) {
def avg = {
assert(self.nonEmpty, "cannot average an empty collection")
num.toDouble(self.sum) / self.size
}
}
然后你可以这样做:
def groupByAndAvg[T, U](ts: Iterable[(T, U)])(implicit num: Numeric[U]) = {
ts.groupByKey.map{ case (k,vs) => k -> vs.avg }
}
groupByAndAvg(Vector(("a", 1), ("a", 2), ("b", 3)))
// res0: scala.collection.immutable.Map[String,Double] = Map(b -> 3.0, a -> 1.5)
答案 1 :(得分:1)
另一种方法(类似):
def groupByAndAvg[T, U](ts: Iterable[(T, U)])(implicit num: Numeric[U]): Iterable[(T, Double)] = {
ts.groupBy(_._1)
.map { case (k, v) =>
(k, v.map(_._2).sum / v.size) }
}