Scala:通用加权平均函数

时间:2017-04-13 01:52:06

标签: scala generics implicit-conversion implicit

我想实现一个通用加权平均函数,它放宽了对值的要求,并且权重属于同一类型。即,我想支持说:(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的权重:

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

Scala编译器会告诉我:

  

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

有没有办法绕过这个?

我试图编写一个NumericCombine类,我使用AB参数化,将类型“组合”为“公共”类型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,“编写“事实证明这种类型比我预期的要困难。请查看this问题以获取更多信息,并查看here以了解NumericCombine的完整实施情况。

更新

作为another question(完整工作演示here)的答案,已经获得了一个令人满意的解决方案,但考虑到{{3}中提出的问题,仍然有一些设计改进的余地。与@ziggystar。

2 个答案:

答案 0 :(得分:3)

线性组合

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

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

修改:您对类型的最常见要求是TS上形成module (wiki),或{{1成为T - 模块。

尖顶

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

VectorSpace / Float无法工作

从这次讨论中可以看出,以及你已经观察到的事实是,Int作为权重,而Float作为元素类型将无法解决,因为整体数字不会在实数上形成向量空间。您必须先将Int提升为Int

通过类型类推广

标量类型只有两个主要候选者,即FloatFloat。 并且主要只有Double是推广的候选者,所以你可以做以下简单而不是一般的解决方案:

Int

答案 1 :(得分:-1)

首先你的模板是错误的。 (对不起,如果&#34;模板&#34;表达式错误 - 我是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)