首页 文章

Scala:通用加权平均函数

提问于
浏览
8

我想实现一个通用加权平均函数,它放宽了对值的要求,并且权重属于同一类型 . 即,我想支持以下序列: (value:Float,weight:Int)(value:Int,weight:Float) 参数而不仅仅是: (value:Int,weight:Int) . [见到我之前的question在此期间 . ]

这就是我目前所拥有的:

def weightedSum[A: Numeric](weightedValues: GenSeq[(A, A)]): (A, A)

def weightedAverage[A: Numeric](weightedValues: GenSeq[(A, A)]): A = {
    val (weightSum, weightedValueSum) = weightedSum(weightedValues)
    implicitly[Numeric[A]] match {
        case num: Fractional[A] => ...
        case num: Integral[A] => ...
        case _ => sys.error("Undivisable numeric!")
    }
}

如果我喂它,例如:

val values:Seq[(Float,Float)] = List((1,2f),(1,3f))
val avg= weightedAverage(values)

但是,如果我没有从 IntFloat 的权重"upcast":

val values= List((1,2f),(1,3f)) //scalac sees it as Seq[(Int,Float)] 
val avg= weightedAverage(values)

Scala编译器会告诉我:

错误:无法找到Numeric类型的证据参数的隐含值[AnyVal] val avg = weightedAverage(values)

有办法绕过这个吗?

我尝试编写一个 NumericCombine 类,我用 AB 参数化了"combines"类型"common"类型 AB (例如,组合 FloatInt 给你 Float ):

abstract class NumericCombine[A: Numeric, B: Numeric] {
    type AB <: AnyVal

    def fromA(x: A): AB
    def fromB(y: B): AB
    val num: Numeric[AB]

    def plus(x: A, y: B): AB = num.plus(fromA(x), fromB(y))
    def minus(x: A, y: B): AB = num.minus(fromA(x), fromB(y))
    def times(x: A, y: B): AB = num.times(fromA(x), fromB(y))
}

我设法使用类型类型模式编写简单的 timesplus 函数,但由于 NumericCombine 引入了路径依赖类型 AB ,"composing"这些类型证明比我预期的更难 . 请查看this问题以获取更多信息,并参见here以完整实施 NumericCombine .

Update

作为another question(完整的工作演示here)的答案已经获得了一个令人满意的解决方案,但考虑到@ziggystar在discussion中提出的要点,仍然有一些设计改进的空间 .

2 回答

  • -1

    首先,你的模板是错误的 . (对不起,如果“模板”表达式错误 - 我是scala的新手) . 你的函数需要两个元素属于同一类型的元组([A:Numeric]),而不是元素属于不同类型的元组([A:数字,B:数字])((Int,Float)vs(Float,浮动))

    无论如何,下面编译并希望在你用你想要的微积分填充它之后会很好地工作 .

    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)
    
  • 3

    线性组合

    我认为通过 S 类型的标量对 T 类型的某些元素进行称重/缩放的更一般的任务是线性组合 . 以下是某些任务的权重约束:

    因此,根据该分类的最一般情况是线性组合 . 根据维基百科,它需要权重 S 为一个字段,而 T 要求vector space超过 S .

    Edit: 对类型的真正最普遍的要求是 T 在环 S 上形成module (wiki),或者 TS 模块 .

    Spire

    您可以使用类型类来设置这些要求 . 还有spire,它已经有 FieldVectorSpace 的类型类 . 我自己从未使用过它,所以你必须自己检查一下 .

    Float / Int不起作用

    从这个讨论中可以明显看出,你已经观察到的是,将 Float 作为权重,而 Int 作为元素类型将无法解决,因为整数不会在实数上形成向量空间 . 你必须首先将 Int 推广到 Float .

    通过类型类推广

    标量类型只有两个主要候选者,即 FloatDouble . 并且主要只有 Int 是推广的候选者,因此您可以将以下内容作为一个简单且不那么通用的解决方案:

    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]) = ???
    

相关问题