Scala 新手关于简单数学数组运算的问题 [英] Newbie Scala question about simple math array operations
问题描述
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 使用这些机制避免装箱/拆箱(例如包装一个 int
在 java.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)
这样的代码在执行时不会对任何 int
s(当然,假设 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 int
s (assuming CC
is specialized, of course!).
Manifest
是一种在 scala 中reified 类型的方法(受 JVM 的类型擦除限制).当您希望在某种类型 T
上泛化一个方法,然后创建一个 T
数组(即 T[]
)时,这特别有用方法内.在 Java 中这是不可能的,因为 new T[]
是非法的.在 Scala 中,这可以使用清单来实现.特别是,在这种情况下,它允许我们构造一个 primitive T-array,如 double[]
或 int[]
.(这太棒了,以防你想知道)
Manifest
s 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 int
s 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 Tuple2
s (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屋!