Scala 新手关于简单数学数组运算的问题 [英] Newbie Scala question about simple math array operations

查看:40
本文介绍了Scala 新手关于简单数学数组运算的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Scala 新手问题:

Newbie Scala Question:

假设我想在 Scala 中执行此 [Java 代码]:

Say I want to do this [Java code] in Scala:

public static double[] abs(double[] r, double[] im) {
  double t[] = new double[r.length];
  for (int i = 0; i < t.length; ++i) {
    t[i] = Math.sqrt(r[i] * r[i] + im[i] * im[i]);
  }
  return t;
}  

并使其通用(因为 Scala 有效地执行了我读过的通用原语).仅依靠核心语言(没有库对象/类、方法等),如何做到这一点?说实话,我根本不知道该怎么做,所以我想这只是一个纯粹的奖励积分问题.

and also make it generic (since Scala efficiently do generic primitives I have read). Relying only on the core language (no library objects/classes, methods, etc), how would one do this? Truthfully I don't see how to do it at all, so I guess that's just a pure bonus point question.

我在尝试做这件简单的事情时遇到了很多问题,我暂时放弃了 Scala.希望一旦我看到 Scala 的方式,我就会有一个啊哈"的时刻.

I ran into sooo many problems trying to do this simple thing that I have given up on Scala for the moment. Hopefully once I see the Scala way I will have an 'aha' moment.

更新:与其他人讨论这个问题,这是我目前找到的最佳答案.

UPDATE: Discussing this with others, this is the best answer I have found so far.

def abs[T](r: Iterable[T], im: Iterable[T])(implicit n: Numeric[T]) = {
   import n.mkNumericOps                                                   
   r zip(im) map(t => math.sqrt((t._1 * t._1 + t._2 * t._2).toDouble))          
}

推荐答案

在 scala 中执行泛型/性能原语实际上涉及两种相关机制,scala 使用这些机制避免装箱/拆箱(例如包装一个 intjava.lang.Integer 中,反之亦然):

Doing generic/performant primitives in scala actually involves two related mechanisms which scala uses to avoid boxing/unboxing (e.g. wrapping an int in a java.lang.Integer and vice versa):

  • @specialize 类型注释
  • 在数组中使用Manifest

specialize 是一个注解,告诉 Java编译器来创建代码的原始"版本(类似于 C++ 模板,所以我被告知).查看Tuple2(这是专门的)与 List(不是).它是在 2.8 中添加的,这意味着,例如像 CC[Int].map(f : Int => Int) 这样的代码在执行时不会对任何 ints(当然,假设 CC 是专门的!).

specialize is an annotation that tells the Java compiler to create "primitive" versions of code (akin to C++ templates, so I am told). Check out the type declaration of Tuple2 (which is specialized) compared with List (which isn't). It was added in 2.8 and means that, for example code like CC[Int].map(f : Int => Int) is executed without ever boxing any ints (assuming CC is specialized, of course!).

Manifest 是一种在 scala 中reified 类型的方法(受 JVM 的类型擦除限制).当您希望在某种类型 T 上泛化一个方法,然后创建一个 T 数组(即 T[])时,这特别有用方法内.在 Java 中这是不可能的,因为 new T[] 是非法的.在 Scala 中,这可以使用清单来实现.特别是,在这种情况下,它允许我们构造一个 primitive T-array,如 double[]int[].(这太棒了,以防你想知道)

Manifests are a way of doing reified types in scala (which is limited by the JVM's type erasure). This is particularly useful when you want to have a method genericized on some type T and then create an array of T (i.e. T[]) within the method. In Java this is not possible because new T[] is illegal. In scala this is possible using Manifests. In particular, and in this case it allows us to construct a primitive T-array, like double[] or int[]. (This is awesome, in case you were wondering)

从性能角度来看,装箱非常重要,因为它会产生垃圾,除非您的所有 int 都是 <127. 显然,它还在额外的流程步骤/方法调用等方面增加了一定程度的间接性.但是请考虑到除非您绝对肯定您肯定会这样做(即大多数代码不会),否则您可能不会发出警报需要这样的微优化)

Boxing is so important from a performance perspective because it creates garbage, unless all of your ints are < 127. It also, obviously, adds a level of indirection in terms of extra process steps/method calls etc. But consider that you probably don't give a hoot unless you are absolutely positively sure that you definitely do (i.e. most code does not need such micro-optimization)

所以,回到问题:为了在没有装箱/拆箱的情况下做到这一点,您必须使用 Array(List 还不是专门的,而且会更多无论如何都需要对象,即使是这样!).一对集合上的 zipped 函数将返回 Tuple2 的集合(不需要装箱,因为它是专门的).

So, back to the question: in order to do this with no boxing/unboxing, you must use Array (List is not specialized yet, and would be more object-hungry anyway, even if it were!). The zipped function on a pair of collections will return a collection of Tuple2s (which will not require boxing, as this is specialized).

为了一般地执行此操作(即跨各种数字类型),您必须要求在您的泛型参数上绑定一个上下文,它是 Numeric 并且 Manifest 可以是找到(创建数组所需).所以我开始沿着......

In order to do this generically (i.e. across various numeric types) you must require a context bound on your generic parameter that it is Numeric and that a Manifest can be found (required for array creation). So I started along the lines of...

def abs[T : Numeric : Manifest](rs : Array[T], ims : Array[T]) : Array[T] = {
    import math._
    val num = implicitly[Numeric[T]]
    (rs, ims).zipped.map { (r, i) => sqrt(num.plus(num.times(r,r), num.times(i,i))) }
    //                               ^^^^ no SQRT function for Numeric
}

...但它不太好.原因是通用"Numeric 值没有像 sqrt -> 这样的操作,所以你只能在知道你有一个 时才能这样做双重.例如:

...but it doesn't quite work. The reason is that a "generic" Numeric value does not have an operation like sqrt -> so you could only do this at the point of knowing you had a Double. For example:

scala> def almostAbs[T : Manifest : Numeric](rs : Array[T], ims : Array[T]) : Array[T] = {
 | import math._
 | val num = implicitly[Numeric[T]]
 | (rs, ims).zipped.map { (r, i) => num.plus(num.times(r,r), num.times(i,i)) }
 | }
almostAbs: [T](rs: Array[T],ims: Array[T])(implicit evidence$1: Manifest[T],implicit     evidence$2: Numeric[T])Array[T]

太棒了 - 现在看看这个纯粹的通用方法做一些事情!

Excellent - now see this purely generic method do some stuff!

scala> val rs = Array(1.2, 3.4, 5.6); val is = Array(6.5, 4.3, 2.1)
rs: Array[Double] = Array(1.2, 3.4, 5.6)
is: Array[Double] = Array(6.5, 4.3, 2.1)

scala> almostAbs(rs, is)
res0: Array[Double] = Array(43.69, 30.049999999999997, 35.769999999999996)

现在我们可以sqrt得到结果,因为我们有一个Array[Double]

Now we can sqrt the result, because we have a Array[Double]

scala> res0.map(math.sqrt(_))
res1: Array[Double] = Array(6.609841147864296, 5.481788029466298, 5.980802621722272)

并证明即使使用另一种 Numeric 类型也能工作:

And to prove that this would work even with another Numeric type:

scala> import math._
import math._
scala> val rs = Array(BigDecimal(1.2), BigDecimal(3.4), BigDecimal(5.6)); val is =     Array(BigDecimal(6.5), BigDecimal(4.3), BigDecimal(2.1))
rs: Array[scala.math.BigDecimal] = Array(1.2, 3.4, 5.6)
is: Array[scala.math.BigDecimal] = Array(6.5, 4.3, 2.1)

scala> almostAbs(rs, is)
res6: Array[scala.math.BigDecimal] = Array(43.69, 30.05, 35.77)

scala> res6.map(d => math.sqrt(d.toDouble))
res7: Array[Double] = Array(6.609841147864296, 5.481788029466299, 5.9808026217222725)

这篇关于Scala 新手关于简单数学数组运算的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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