2012-04-15 14 views
17

Parece un problema fácil para cualquier tipo específico de número, es decir, doble/entero, pero es difícil de escribir en el caso general.¿Cómo implementar la función media genérica en scala?

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

¿Cómo implementar esto para cualquier tipo de número (Int, Float, Double, BigDecemial)?

Respuesta

29

usted tiene que pasar una implícita Numeric que permitirá la suma y la conversión a doble:

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

El compilador proporcionará la instancia correcta para usted:

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 

Puede el uso de la función para definir una vista implícita (siempre que propague la dependencia numérica implícita):

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

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

'clase implícita IterebleWithAvg [T: Numérico] (datos: Iterable [T]) {def Promedio = Media (datos)}' debe ser preferido a partir de hoy. –

+0

También deberíamos manejar iterable de longitud cero, lanzando un error. – Marboni

13

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 
Cuestiones relacionadas