Velvet Star Monitor

Standout celebrity highlights with iconic style.

updates

How to implement generic average function in scala?

Writer Emily Wong

It seems easy problem for any specific kind of Number i.e. Double/Integer but it is hard to write in general case.

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

How to implement this for any kind of Number(Int,Float,Double,BigDecemial)?

2 Answers

You have to pass an implicit Numeric which will allow summation and conversion to Double:

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

The compiler will provide the correct instance for you:

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

You can the use the function to define an implicit view (provided you propagate the implicit numeric dependency):

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

Here's the way I define it in my code.

Instead of using Numeric, I use Fractional, since Fractional defines a division operation (Numeric doesn't necessarily have division). This means that when you call .avg, you will get back the same type you put in, instead of always getting Double.

I also define it over all GenTraversableOnce collections so that it works on, for example, 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)

Notice how if we give it a collection of Double, we get back Double and if we give it BigDecimal, we get back BigDecimal. We could even define our own Fractional number type (which I do occasionally), and it will work for that.

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

However, Int is not a kind of Fractional, meaning that it doesn't make sense to get an Int and the result of averaging Ints, so we have to have a special case for Int that converts to 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)

So averaging Ints gives us a Double:

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

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy