我很好奇我如何编写一个通用方法来计算scala中的标准偏差和方差.我有一个计算均值的通用方法(从这里被盗:在Scala中编写通用的平均函数)
我试图将平均值计算转换为标准偏差和方差但我看起来不对.此外,泛型在Scala编程方面超越了我的技能.
计算均值,标准差和方差的代码如下:
package ca.mikelavender
import scala.math.{Fractional, Integral, Numeric, _}
package object genericstats {
def stdDev[T: Numeric](xs: Iterable[T]): Double = sqrt(variance(xs))
def variance[T: Numeric](xs: Iterable[T]): Double = implicitly[Numeric[T]] match {
case num: Fractional[_] => {
val avg = mean(xs)
num.toDouble(
xs.foldLeft(num.zero)((b, a) =>
num.plus(b, num.times(num.minus(a, avg), num.minus(a, avg))))) /
xs.size
}
case num: Integral[_] => {
val avg = mean(xs)
num.toDouble(
xs.foldLeft(num.zero)((b, a) =>
num.plus(b, num.times(num.minus(a, avg), num.minus(a, avg))))) /
xs.size
}
}
/**
* https://stackoverflow.com/questions/6188990/writing-a-generic-mean-function-in-scala
*/
def mean[T: Numeric](xs: Iterable[T]): T = implicitly[Numeric[T]] match {
case num: Fractional[_] => import num._; xs.sum / fromInt(xs.size)
case num: Integral[_] => import num._; xs.sum / fromInt(xs.size)
case _ => sys.error("Undivisable numeric!")
}
}
Run Code Online (Sandbox Code Playgroud)
我觉得不需要方差方法中的匹配情况,或者可能更优雅.也就是说,代码的重复性对我来说似乎非常错误,我应该能够使用匹配来获取数字类型,然后将其传递给执行计算的单个代码块.
我不喜欢的另一件事是它总是返回一个Double.我觉得应该返回相同的输入数字类型,至少对于Fractional值.
那么,有没有关于如何改进代码并使其更漂亮的建议?
Tra*_*own 20
类型类的目标是为类型Numeric提供一组操作,以便您可以编写在具有类型类实例的任何类型上一般工作的代码.Numeric提供了一组操作及其子类,Integral并Fractional另外提供了更具体的操作(但它们也表征了更少的类型).如果您不需要这些更具体的操作,您可以简单地工作Numeric,但不幸的是在这种情况下您可以.
让我们开始吧mean.这里的问题是除法对于整数和分数类型意味着不同的东西,并且根本不提供类型Numeric.您从Daniel 链接的答案通过调度Numeric实例的运行时类型来解决此问题,并且如果实例不是a Fractional或者,则只是崩溃(在运行时)Integral.
我将不同意丹尼尔(或者至少是五年前的丹尼尔),并说这不是一个很好的方法 - 它既是真正的差异,也是同时抛出大量的类型安全.我认为有三种更好的解决方案.
您可能会认为取整数对于整数类型没有意义,因为积分除失了结果的小数部分,并且仅为小数类型提供:
def mean[T: Fractional](xs: Iterable[T]): T = {
val T = implicitly[Fractional[T]]
T.div(xs.sum, T.fromInt(xs.size))
}
Run Code Online (Sandbox Code Playgroud)
或者使用漂亮的隐式语法:
def mean[T: Fractional](xs: Iterable[T]): T = {
val T = implicitly[Fractional[T]]
import T._
xs.sum / T.fromInt(xs.size)
}
Run Code Online (Sandbox Code Playgroud)
最后一个句法点:如果我发现我必须编写implicitly[SomeTypeClass[A]]以获取对类型类实例的引用,我倾向于去除上下文绑定([A: SomeTypeClass]部分)以清理一些东西:
def mean[T](xs: Iterable[T])(implicit T: Fractional[T]): T =
T.div(xs.sum, T.fromInt(xs.size))
Run Code Online (Sandbox Code Playgroud)
不过,这完全是品味问题.
您还可以mean返回一个具体的小数类型Double,并Numeric在执行操作之前将值转换为该类型:
def mean[T](xs: Iterable[T])(implicit T: Numeric[T]): Double =
T.toDouble(xs.sum) / xs.size
Run Code Online (Sandbox Code Playgroud)
或者,等效但使用以下toDouble语法Numeric:
import Numeric.Implicits._
def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size
Run Code Online (Sandbox Code Playgroud)
这为积分和分数类型(最高精度Double)提供了正确的结果,但代价是使您的操作不那么通用.
最后,你可以创建一个新类型的类,用于提供共享的除法运算Fractional和Integral:
trait Divisible[T] {
def div(x: T, y: T): T
}
object Divisible {
implicit def divisibleFromIntegral[T](implicit T: Integral[T]): Divisible[T] =
new Divisible[T] {
def div(x: T, y: T): T = T.quot(x, y)
}
implicit def divisibleFromFractional[T](implicit T: Fractional[T]): Divisible[T] =
new Divisible[T] {
def div(x: T, y: T): T = T.div(x, y)
}
}
Run Code Online (Sandbox Code Playgroud)
然后:
def mean[T: Numeric: Divisible](xs: Iterable[T]): T =
implicitly[Divisible[T]].div(xs.sum, implicitly[Numeric[T]].fromInt(xs.size))
Run Code Online (Sandbox Code Playgroud)
这本质上是原始的更原则的版本 - 而mean不是在运行时调度子类型,您使用新类型来表征子类型.有更多的代码,但没有运行时错误的可能性(除非当然xs是空的等等,但这是所有这些方法都遇到的正交问题).
这三种方式,我可能会选择第二种,而你的情况似乎是,因为你特别适合variance和stdDev已经返回Double.在这种情况下,整个事情看起来像这样:
import Numeric.Implicits._
def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size
def variance[T: Numeric](xs: Iterable[T]): Double = {
val avg = mean(xs)
xs.map(_.toDouble).map(a => math.pow(a - avg, 2)).sum / xs.size
}
def stdDev[T: Numeric](xs: Iterable[T]): Double = math.sqrt(variance(xs))
Run Code Online (Sandbox Code Playgroud)
......你已经完成了.
在实际代码中,我可能会查看像Spire这样的库,而不是使用标准库的类型类.