Scala:通用加权平均函数 [英] Scala: generic weighted average function

查看:160
本文介绍了Scala:通用加权平均函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要实现一个通用的加权平均函数,这个函数可以放宽对数值的要求,而权重是相同类型的。即我想支持如下的序列:(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 加上函数基于此类型类型模式,但是自 NumericCombine 引入了一个路径依赖类型 AB ,构成这些类型被证明比我预期的更困难。请参阅问题以获取更多信息并请参阅此处全面实施 NumericCombine 。 p>

更新



一个令人满意的解决方案已经作为另一个问题(完整工作演示 here )但是,考虑到讨论与@ziggystar。 解决方案

线性组合



<我>对于类型为 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 to Float:

    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 with A and B which "combines" the types into a "common" type AB (for example, combining Float and Int gives you 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))
    }
    

    and I managed to write simple times and plus functions based on this with the typeclass pattern, but since NumericCombine introduces a path-dependent type AB, "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 of NumericCombine.

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

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

    这篇关于Scala:通用加权平均函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆