【问题标题】:How does one write the Pythagoras Theorem in Scala?如何在 Scala 中编写毕达哥拉斯定理?
【发布时间】:2010-03-06 00:48:17
【问题描述】:

直角三角形的斜边的平方等于其他两条边的平方和。

这是毕达哥拉斯定理。根据边长“a”和“b”计算斜边的函数将返回 sqrt(a * a + b * b)。

问题是,您将如何在 Scala 中定义这样一个函数,使其可以与任何实现适当方法的类型一起使用?

对于上下文,想象一下您想要使用 Int、Double、Int-Rational、Double-Rational、BigInt 或 BigInt-Rational 类型的整个数学定理库,具体取决于您正在做什么,以及速度、精度和准确度和范围要求。

【问题讨论】:

标签: scala


【解决方案1】:

这仅适用于 Scala 2.8,但确实有效:

scala> def pythagoras[T](a: T, b: T, sqrt: T => T)(implicit n: Numeric[T]) = {
     | import n.mkNumericOps
     | sqrt(a*a + b*b)
     | }
pythagoras: [T](a: T,b: T,sqrt: (T) => T)(implicit n: Numeric[T])T

scala> def intSqrt(n: Int) = Math.sqrt(n).toInt
intSqrt: (n: Int)Int

scala> pythagoras(3,4, intSqrt)
res0: Int = 5

更一般地说,特征Numeric 实际上是关于如何解决此类问题的参考。另见Ordering

【讨论】:

  • 您可以更新这个流行的答案以使用上下文边界,因为它们存在。
  • @BrianMcCutchon 我相信上下文边界实际上在 2.8 上可用,但是我需要将它们分配给一个变量,以便我可以从中导入 mkNumericOps。不过,现在有更好的解决方案——不过,如果您需要,请在单独的问题中提出。
  • 你指的是import Numeric.Implicits._吗?这将允许您在没有证据变量的情况下使用上下文边界。
【解决方案2】:

最明显的方式:

type Num = {
  def +(a: Num): Num
  def *(a: Num): Num
}

def pyth[A <: Num](a: A, b: A)(sqrt: A=>A) = sqrt(a * a + b * b)

// usage
pyth(3, 4)(Math.sqrt)

这很可怕,原因有很多。首先,我们遇到了递归类型的问题,Num。仅当您编译此代码并将 -Xrecursive 选项设置为某个整数值(对于数字而言,5 可能绰绰有余)时才允许这样做。其次,Num 类型是结构性的,这意味着对其定义的成员的任何使用都将编译为相应的反射调用。说得客气一点,这个版本的pyth 效率极低,运行速度比传统实现慢了几十万 倍。如果您想为定义+* 并且存在sqrt 函数的any 类型定义pyth,则无法绕过结构类型。

最后,我们来到了最基本的问题:它过于复杂。为什么要以这种方式实现该功能?实际上,它需要应用的唯一类型是真实的 Scala 数字。因此,最简单的方法是执行以下操作:

def pyth(a: Double, b: Double) = Math.sqrt(a * a + b * b)

所有问题都解决了!由于隐式转换的奇迹,这个函数可用于DoubleIntFloat 类型的值,甚至像Short 这样的奇怪值。虽然这个函数在技术上确实不如我们的结构类型版本灵活,但它大大更高效且可读性更强。我们可能已经失去了为定义 +* 的不可预见类型计算勾股定理的能力,但我认为您不会错过这种能力。

【讨论】:

  • “简单”解决方案是否适用于 BigNum 或 Rational?我可以定义一个完整的数学定理库并让它们被 double、integer、bignum 或有理数使用吗?
  • +1 用于实现和不应该这样做的原因。 :-)
  • 现在我对 Scala 有了更多的了解,我发现存在另一个解决方案。定义一个抽象 Num 类,任何所需类型的子类,从所需类型到相应子类的隐式转换,并使 pyth[A] 接受 A 的“a”和“b”,加上来自 A => Num[A 的隐式]。您介意将此解决方案添加到您的答案中吗?我愿意接受,但我希望它更完整。
  • 我必须指出,此解决方案不适用于长值。并非所有 long 值都可以用双精度表示。
【解决方案3】:

对丹尼尔的回答的一些想法:

我已经experimentedNumeric 泛化为Real,这样更适合这个函数提供sqrt 函数。这将导致:

def pythagoras[T](a: T, b: T)(implicit n: Real[T]) = {
   import n.mkNumericOps
   (a*a + b*b).sqrt
}

在这样的通用函数中使用文字数字很棘手,但可能。

def pythagoras[T](a: T, b: T)(sqrt: (T => T))(implicit n: Numeric[T]) = {
   import n.mkNumericOps
   implicit val fromInt = n.fromInt _

   //1 * sqrt(a*a + b*b)   Not Possible!
   sqrt(a*a + b*b) * 1    // Possible
}

如果 sqrt 在第二个参数列表中传递,类型推断会更好。

参数ab 将作为对象传递,但@specialized 可以解决这个问题。不幸的是,数学运算仍然会有一些开销。

几乎可以在不导入 mkNumericOps 的情况下做到这一点。我得到了frustratringly close!

【讨论】:

  • 1 和 0 对于任何人来说都应该足够了! :)
  • 我也想要eπi,这样我就可以表达欧拉恒等式了。
  • 嘿,我想知道为什么我必须这样做:import n._; implicit val fromInt = n.fromInt _; x * x + 1 才能获得像 x * x + 1 这样的表达式来编译。为什么只有import n._ 还不够?因为该导入包括 n.fromInt,不是吗?当我尝试将其添加到类型 T 时,不应该在 Int 上隐式调用 n.fromInt 吗?
  • @JohnPeterThompsonGarcés 因为n.fromInt 没有在Numeric 中定义为隐式方法,所以你必须自己做。此外,隐含的 fromInt 不适用于例如1 + x,因为1 必须先转换为T,然后再转换为Ops,而Scala 不允许您像这样链接转换(我敢肯定,这是有充分理由的)。不过,您可以自己定义一个类型来提供所有这些,包括返回 OpsfromInt 版本。
【解决方案4】:

java.lang.Math中有一个方法:

public static double hypot (double x, double y)

javadocs 断言的:

返回没有中间溢出或下溢的 sqrt(x2 +y2)。

查看 src.zip,Math.hypot 使用 StrictMath,这是一种原生方法:

public static native double hypot(double x, double y);

【讨论】:

  • 如何将它与 Int 或 BigDecimal 一起使用?我不关心计算假设,我关心的是我如何做通用数学。
  • 对不起。那么,它应该只是一个旁注。
猜你喜欢
  • 2020-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多