Scala:通用加权平均函数 [英] Scala: generic weighted average function
问题描述
我想要实现一个通用的加权平均函数,这个函数可以放宽对数值的要求,而权重是相同类型的。即我想支持如下的序列:(value:Float,weight:Int)
和(value:Int,weight:Float) code>参数,而不仅仅是:
(value:Int,weight:Int)
。 [查看我之前的问题, 。
这是我现在有的:
def weightedSum [A:Numeric](weightedValues:GenSeq [(A,A)]):(A,A)
def weightedAverage [A:Numeric](weightedValues:GenSeq [(A,A)]) :A = {
val(weightSum,weightedValueSum)= weightedSum(weightedValues)
隐式地[Numeric [A]] match {
case num:Fractional [A] => ...
case num:Integral [A] => ...
案例_ => sys.error(Undivisable numeric!)
}
}
举例来说,如果我喂食它,完美的作品:
val值:Seq [(Float,Float)] = List((1, 2f),(1,3f))
val avg = weightedAverage(values)
然而,如果我没有将权重从 Int
转换为 Float
:
val values = List((1,2f),(1,3f))// scalac将其视为Seq [(Int,Float)]
val avg = weightedAverage(values)
Scala编译器会告诉我:
错误:无法找到类型为
的证据参数的隐式值数字[AnyVal]
val avg = weightedAverage(values) p>
有没有办法绕过这个?
使用 A
和 B
来编写一个 NumericCombine
它将这些类型结合为一种常见 类型 AB
(例如,合并 Float
和 Int
给你 Float
):
抽象类NumericCombine [A:Numeric, B:Numeric] {
type AB <:AnyVal
def fromA(x:A):AB
def fromB(y:B):AB
val num:数字[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))
}
我设法写简单的 times
和加上函数基于此类型类型模式,但是自
引入了一个路径依赖类型 AB
,构成这些类型被证明比我预期的更困难。请参阅此问题以获取更多信息并请参阅此处全面实施 NumericCombine
。 p>
更新
线性组合
<我>对于类型为
S
的标量,称量/缩放类型 T
的某些元素的更一般任务是线性组合。以下是对某些任务权重的限制: - 线性组合:不受限制
- 仿射组合:权重总和为1
- 规范组合/加权平均值:权重是非负的
- 凸组合:权重总和为1且不为负
因此,根据此分类的最一般情况是线性组合。
根据维基百科的说法,它要求权重S
是一个字段,T 在 S
之上的向量空间。
编辑:对类型的真正最普遍的要求是
T
通过环S <>形成一个模块(wiki)。 / code>或
T
是S
- 模块。
Spire
你可以用类型类设置这些需求。还有 spire ,其中已经有用于
Field
的类型类>和VectorSpace
。
浮动
/ <$ c $我从来没有使用过它自己,所以您必须亲自查看。 c> Int 不会工作
从这个讨论中也可以看出,你已经观察到的事实是, code> Float 作为权重,而
Int
,因为元素类型不会生效,因为整数不构成向量空间超过实际。您必须首先将Int
提升为Float
。
< h2>通过类型类别提升
标量类型只有两个主要候选者,即
Float
和双
。
主要只有Int
是促销的候选人,因此您可以将以下内容作为一个简单而不常用的解决方案:case class Promotable [R,T](promotion:R => T)
对象Promotable {
implicit val intToFloat = Promotable [Int,Float](_。toFloat)
implicit val floatToDouble = Promotable [Float,Double](_。toDouble)
隐式val intToDouble = Promotable [Int,Double](_。toDouble )
implicit def identityInst [A] = Promotable [A,A](identity)
}作为一个小解决方案,您可以编写一个类型类型
def加权平均[S,VS](值:Seq [(S,VS)])(隐式p:Promotable [VS,S])= ???
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 of say:
(value:Float,weight:Int)
and(value:Int,weight:Float)
arguments and not just:(value:Int,weight:Int)
. [See my earlier question in the run up to this.]This is what I currently have:
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!") } }
This works perfectly if I feed it for example:
val values:Seq[(Float,Float)] = List((1,2f),(1,3f)) val avg= weightedAverage(values)
However if I don't "upcast" the weights from
Int
toFloat
:val values= List((1,2f),(1,3f)) //scalac sees it as Seq[(Int,Float)] val avg= weightedAverage(values)
Scala compiler will tell me:
error: could not find implicit value for evidence parameter of type Numeric[AnyVal]
val avg= weightedAverage(values)Is there a way of getting round this?
I had an attempt at writing a
NumericCombine
class that I parameterized withA
andB
which "combines" the types into a "common" typeAB
(for example, combiningFloat
andInt
gives youFloat
) :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)) }
and I managed to write simple
times
andplus
functions based on this with the typeclass pattern, but sinceNumericCombine
introduces a path-dependent typeAB
, "composing" the types is proving to be more difficult than I expected. look at this question for more information and see here for the full implementation ofNumericCombine
.Update
A somewhat satisfactory solution has been obtained as an answer to another question (full working demo here) however there is still room for some design improvement taking into account the points raised in the discussion with @ziggystar.
解决方案Linear combinations
I think the more general task that involves weighing/scaling some elements of type
T
by a scalar of typeS
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, andT
to form a vector space overS
.Edit: The truly most general requirement you can have on the types is that
T
forms a module (wiki) over the ringS
, orT
being anS
-module.Spire
You could set up these requirements with typeclasses. There's also spire, which already has typeclasses for
Field
andVectorSpace
. 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, andInt
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 promoteInt
toFloat
first.Promotion via typeclass
There are only two major candidates for the scalar type, i.e.,
Float
andDouble
. And mainly onlyInt
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]) = ???
这篇关于Scala:通用加权平均函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!