如何在scala中实现通用平均函数?

时间:2012-04-15 07:25:10

标签: scala scala-collections

对于任何特定类型的数字,即Double / Integer,似乎很容易出问题,但在一般情况下很难写。

implicit def iterebleWithAvg(data:Iterable[Double]) = new {
    def avg:Double = data.sum / data.size
}

如何为任何类型的数字(Int,Float,Double,BigDecemial)实现这个?

2 个答案:

答案 0 :(得分:30)

你必须传递一个隐含的Numeric,它允许求和并转换为Double:

def average[T]( ts: Iterable[T] )( implicit num: Numeric[T] ) = {
  num.toDouble( ts.sum ) / ts.size
}

编译器将为您提供正确的实例:

scala> average( List( 1,2,3,4) )
res8: Double = 2.5

scala> average( 0.1 to 1.1 by 0.05 )
res9: Double = 0.6000000000000001

scala> average( Set( BigInt(120), BigInt(1200) ) )
res10: Double = 660.0

您可以使用该函数定义隐式视图(假设您传播隐式数字依赖项):

implicit def iterebleWithAvg[T:Numeric](data:Iterable[T]) = new {
  def avg = average(data)
}

scala> List(1,2,3,4).avg
res13: Double = 2.5

答案 1 :(得分:13)

这是我在代码中定义它的方式。

我使用Numeric而不是Fractional,因为Fractional定义了一个除法运算(Numeric并不一定有除法)。这意味着当您拨打.avg时,您将获得与之相同的类型,而不是始终获得Double

我还在所有GenTraversableOnce个集合中对其进行了定义,以便它可以使用,例如Iterator

class EnrichedAvgFractional[A](self: GenTraversableOnce[A]) {
  def avg(implicit num: Fractional[A]) = {
    val (total, count) = self.toIterator.foldLeft((num.zero, num.zero)) {
      case ((total, count), x) => (num.plus(total, x), num.plus(count, num.one))
    }
    num.div(total, count)
  }
}
implicit def enrichAvgFractional[A: Fractional](self: GenTraversableOnce[A]) = new EnrichedAvgFractional(self)

请注意,如果我们为其提供Double的集合,我们会返回Double,如果我们提供BigDecimal,我们会返回BigDecimal。我们甚至可以定义我们自己的Fractional数字类型(我偶尔也会这样做),它将适用于此。

scala> Iterator(1.0, 2.0, 3.0, 4.0, 5.0).avg
res0: Double = 3.0

scala> Iterator(1.0, 2.0, 3.0, 4.0, 5.0).map(BigDecimal(_)).avg
res1: scala.math.BigDecimal = 3.0

但是,Int不是Fractional的一种,这意味着获得Int和平均Int s的结果没有意义,所以我们必须有Int的特殊情况转换为Double

class EnrichedAvgInt(self: GenTraversableOnce[Int]) {
  def avg = {
    val (total, count) = self.toIterator.foldLeft(0, 0) {
      case ((total, count), x) => (total + x, count + 1)
    }
    total.toDouble / count
  }
}
implicit def enrichAvgInt(self: GenTraversableOnce[Int]) = new EnrichedAvgInt(self)

所以平均Int s为我们提供Double

scala> Iterator(1, 2, 3, 4, 5).avg
res2: Double = 3
相关问题