I want to implement a generic weighted average function which relaxes the requirement on the values and the weights being of the same type. ie, I want to support sequences o
First of all your template is wrong. (And sorry if the "template" expression is wrong - I am a novice in scala). Your functions expect tuples where both elements are of the same type ( [A: Numeric] ) , instead of tuples where elements are of different types ( [A: Numeric, B: Numeric] ) ( (Int, Float) vs (Float, Float) )
Anyway the below compiles and hopefully will work well after you fill it with your desired calculus.
import scala.collection._
def weightedSum[A: Numeric, B: Numeric](weightedValues: GenSeq[(A,B)]): (A,B) = {
weightedValues.foldLeft((implicitly[Numeric[A]].zero, implicitly[Numeric[B]].zero)) { (z, t) =>
( implicitly[Numeric[A]].plus(z._1, t._1),
implicitly[Numeric[B]].plus(z._2, t._2)
)
}
}
def weightedAverage[A: Numeric, B: Numeric](weightedValues: GenSeq[(A,B)]): A = {
val (weightSum, weightedValueSum) = weightedSum(weightedValues)
implicitly[Numeric[A]] match {
case num: Fractional[A] => implicitly[Numeric[A]].zero
case num: Integral[A] => implicitly[Numeric[A]].zero
case _ => sys.error("Undivisable numeric!")
}
}
val values1: Seq[(Float, Float)] = List((1, 2f), (1, 3f))
val values2: Seq[(Int, Float)] = List((1, 2f), (1, 3f))
val wa1 = weightedAverage(values1)
val wa2 = weightedAverage(values2)
I think the more general task that involves weighing/scaling some elements of type T
by a scalar of type S
is that of a linear combination. Here are the constraints on the weights for some tasks:
So the most general case according to this classification is the linear combination.
According to Wikipedia, it requires the weights, S
, to be a field, and T
to form a vector space over S
.
Edit: The truly most general requirement you can have on the types is that T
forms a module (wiki) over the ring S
, or T
being an S
-module.
You could set up these requirements with typeclasses. There's also spire, which already has typeclasses for Field
and VectorSpace
. I have never used it myself, so you have to check it out yourself.
Float
/Int
won't workWhat is also apparent from this discussion, and what you have already observed, is the fact that having Float
as a weight, and Int
as the element type will not work out, as the whole numbers do not form a vector space over the reals. You'd have to promote Int
to Float
first.
There are only two major candidates for the scalar type, i.e., Float
and Double
.
And mainly only Int
is a candidate for promoting, so you could do the following as a simple and not so general solution:
case class Promotable[R,T](promote: R => T)
object Promotable {
implicit val intToFloat = Promotable[Int,Float](_.toFloat)
implicit val floatToDouble = Promotable[Float,Double](_.toDouble)
implicit val intToDouble = Promotable[Int,Double](_.toDouble)
implicit def identityInst[A] = Promotable[A,A](identity)
}As a "small" solution you could write a typeclass
def weightedAverage[S,VS](values: Seq[(S,VS)])(implicit p: Promotable[VS,S]) = ???