Así es como lo defino en mi código.
En lugar de utilizar Numeric
, utilizo Fractional
, ya Fractional
define una operación de división (Numeric
no necesariamente tiene división). Esto significa que cuando llame al .avg
, obtendrá el mismo tipo que ingresó, en lugar de obtener siempre Double
.
También lo defino en todas las colecciones GenTraversableOnce
para que funcione en, por ejemplo, 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)
Note como si le damos una colección de Double
, volvamos Double
y si le damos BigDecimal
, volvamos BigDecimal
. Incluso podríamos definir nuestro propio tipo de número Fractional
(que hago de vez en cuando), y funcionará para eso.
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
Sin embargo, Int
no es una especie de Fractional
, lo que significa que no tiene sentido para obtener una Int
y el resultado de promediar Int
s, por lo que tenemos que tener un caso especial para Int
que se convierte en a 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)
Así promedio Int
s nos da una Double
:
scala> Iterator(1, 2, 3, 4, 5).avg
res2: Double = 3
'clase implícita IterebleWithAvg [T: Numérico] (datos: Iterable [T]) {def Promedio = Media (datos)}' debe ser preferido a partir de hoy. –
También deberíamos manejar iterable de longitud cero, lanzando un error. – Marboni