Scala:计算标准偏差的通用方法是什么

时间:2016-09-21 12:56:17

标签: scala generics

我很好奇如何编写一个通用方法来计算scala中的标准偏差和方差。我有一个计算平均值的通用方法(从这里被盗:Writing a generic mean function in Scala

我试图将平均值计算转换为标准偏差和方差但我看起来不对。此外,泛型超出了我在Scala编程方面的技能。

计算均值,标准差和方差的代码如下:

<table><tr><td id='clock'></td></tr></table>

我觉得不需要方差方法中的匹配情况,或者可能更优雅。也就是说,代码的重复性对我来说似乎非常错误,我应该能够使用匹配来获取数字类型,然后将其传递给执行计算的单个代码块。

我不喜欢的另一件事是它总是返回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!") } } 。我觉得它应该返回相同的输入数字类型,至少对于小数值。

那么,有没有关于如何改进代码并使其更漂亮的建议?

2 个答案:

答案 0 :(得分:20)

Numeric这样的类型类的目标是为类型提供一组操作,以便您可以编写在具有类型类实例的任何类型上一般工作的代码。 Numeric提供了一组操作,其子类IntegralFractional另外提供了更具体的操作(但它们也表征了更少的类型)。如果您不需要这些更具体的操作,您只需在Numeric级别工作,但不幸的是,在这种情况下,您可以这样做。

让我们从mean开始吧。这里的问题是除法对于整数和小数类型意味着不同的东西,并且对于仅Numeric的类型根本不提供。来自Daniel的answer you've linked通过调度Numeric实例的运行时类型来解决此问题,如果实例不是Fractional或{{{},那么就会崩溃(在运行时) 1}}。

我将不同意丹尼尔(或者至少是五年前的丹尼尔),并说这不是一个很好的方法 - 它既是真正的差异,也是同时抛出很多类型的安全。我认为有三种更好的解决方案。

仅为小数类型

提供这些操作

你可能会认为取整数对整数类型没有意义,因为积分除失了结果的小数部分,只为分数类型提供:

Integral

或者使用漂亮的隐式语法:

def mean[T: Fractional](xs: Iterable[T]): T = {
  val T = implicitly[Fractional[T]]

  T.div(xs.sum, T.fromInt(xs.size))
}

最后一个句法要点:如果我发现我必须编写def mean[T: Fractional](xs: Iterable[T]): T = { val T = implicitly[Fractional[T]] import T._ xs.sum / T.fromInt(xs.size) } 来获取对类型类实例的引用,我倾向于去除上下文绑定(implicitly[SomeTypeClass[A]]部分)来清理事物一点:

[A: SomeTypeClass]

但这完全是一种品味问题。

返回具体的小数类型

您还可以让def mean[T](xs: Iterable[T])(implicit T: Fractional[T]): T = T.div(xs.sum, T.fromInt(xs.size)) 返回类似mean的具体小数类型,并在执行操作之前将Double值转换为该类型:

Numeric

或者,等效但使用def mean[T](xs: Iterable[T])(implicit T: Numeric[T]): Double = T.toDouble(xs.sum) / xs.size 的{​​{1}}语法:

toDouble

这为积分和小数类型提供了正确的结果(最高精度为Numeric),但代价是使您的操作不那么通用。

创建新类型

最后,您可以创建一个新类型类,为import Numeric.Implicits._ def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size 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) } } 的更原则的版本 - 而不是在运行时调度子类型,而是使用新类型来表征子类型。有更多的代码,但没有运行时错误的可能性(当然除非def mean[T: Numeric: Divisible](xs: Iterable[T]): T = implicitly[Divisible[T]].div(xs.sum, implicitly[Numeric[T]].fromInt(xs.size)) 是空的等等,但这是所有这些方法都会遇到的正交问题)。

结论

在这三种方法中,我可能会选择第二种方法,因为您的meanxs已经返回variance,因此在您的情况下这似乎是特别合适的。在这种情况下,整个事情看起来像这样:

stdDev

......你已经完成了。

在实际代码中,我可能会查看类似Spire的库,而不是使用标准库的类型类。

答案 1 :(得分:0)

特拉维斯·布朗的答案写得很好,但是,你可以简单地将Numeric[A]作为隐含参数

def StandardDeviation[A](a: Seq[A])(implicit num: Numeric[A]):Double = {

def mean(a: Seq[A]): Double = num.toDouble(a.sum) / a.size 

def variance(a: Seq[A]): Double = {
  val avg = mean(a)
  a.map(num.toDouble).map(x => math.pow((x - avg),2)).sum / a.size 
}

math.sqrt(variance(a))

}`