我很好奇如何编写一个通用方法来计算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!")
}
}
。我觉得它应该返回相同的输入数字类型,至少对于小数值。
那么,有没有关于如何改进代码并使其更漂亮的建议?
答案 0 :(得分:20)
像Numeric
这样的类型类的目标是为类型提供一组操作,以便您可以编写在具有类型类实例的任何类型上一般工作的代码。 Numeric
提供了一组操作,其子类Integral
和Fractional
另外提供了更具体的操作(但它们也表征了更少的类型)。如果您不需要这些更具体的操作,您只需在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))
是空的等等,但这是所有这些方法都会遇到的正交问题)。
在这三种方法中,我可能会选择第二种方法,因为您的mean
和xs
已经返回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))
}`