按元组分组和平均分组

Yan*_*san 1 scala scala-collections

我需要一个按元组列表分组和平均的方法.

这是我目前的实施:

  // (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 }
  }
Run Code Online (Sandbox Code Playgroud)

有没有一个更好的方式在性能或简单性?

dhg*_*dhg 6

  1. 您可以使用一个映射而不是两个映射来完成.
  2. unzip当你只需要它的一半结果时,没有理由生成两个集合.只是做就.map(_._2)应该得到你所需要的.
  3. 也许更重要的是,如果您计划重复访问值,您可能希望使用map而不是mapValues因为mapValues只创建新映射的视图,这意味着将在每次访问时重新计算平均值.
  4. 最后,你不想声明返回类型,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)
Run Code Online (Sandbox Code Playgroud)

自己实现的东西版本:

如果你经常做这样的事情,你可以定义自己的收集方法.在这里我定义groupByKey,它取a Traverable[(K,V)]并返回a 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
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

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)
Run Code Online (Sandbox Code Playgroud)