按元组分组和平均分组

时间:2014-01-30 14:21:29

标签: 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 }
  }

在性能或简单方面有没有更好的方法?

2 个答案:

答案 0 :(得分:6)

  1. 您可以使用一个映射而不是两个映射。
  2. 当您只需要其结果的一半时,unzip没有理由产生两个集合。只做.map(_._2)就可以得到你所需要的东西。
  3. 也许更重要的是,如果您计划重复访问这些值,则可能需要使用map而不是mapValues,因为mapValues只会创建视图新地图的含义,意味着每次访问都会重新计算平均值。
  4. 最后,您不希望将返回类型声明为Iterable,因为您失去了groupBy为您提供Map的事实。
  5. 所以也许你想要这个:

    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) }
  }