Scala: generic weighted average function

后端 未结 2 1804
旧时难觅i
旧时难觅i 2020-12-11 17:00

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

相关标签:
2条回答
  • 2020-12-11 17:09

    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)
    
    0 讨论(0)
  • 2020-12-11 17:28

    Linear combinations

    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:

    • linear combination: no constraints
    • affine combination: weights sum to one
    • canonical combination/weighted average: weights are non-negative
    • convex combination: weights sum to one and are non-negative

    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.

    Spire

    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 work

    What 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.

    Promotion via typeclass

    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]) = ???
    
    0 讨论(0)
提交回复
热议问题