新手Scala关于简单数学数组操作的问题
新手Scala问题: 假设我想在Scala中执行此[Java代码]:新手Scala关于简单数学数组操作的问题,scala,Scala,新手Scala问题: 假设我想在Scala中执行此[Java代码]: 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; } 使用zip和map:
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;
}
使用
zip
和map
:
scala> val reals = List(1.0, 2.0, 3.0)
reals: List[Double] = List(1.0, 2.0, 3.0)
scala> val imags = List(1.5, 2.5, 3.5)
imags: List[Double] = List(1.5, 2.5, 3.5)
scala> reals zip imags
res0: List[(Double, Double)] = List((1.0,1.5), (2.0,2.5), (3.0,3.5))
scala> (reals zip imags).map {z => math.sqrt(z._1*z._1 + z._2*z._2)}
res2: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)
scala> def abs(reals: List[Double], imags: List[Double]): List[Double] =
| (reals zip imags).map {z => math.sqrt(z._1*z._1 + z._2*z._2)}
abs: (reals: List[Double],imags: List[Double])List[Double]
scala> abs(reals, imags)
res3: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)
更新
最好使用zipped
,因为这样可以避免创建临时集合:
scala> def abs(reals: List[Double], imags: List[Double]): List[Double] =
| (reals, imags).zipped.map {(x, y) => math.sqrt(x*x + y*y)}
abs: (reals: List[Double],imags: List[Double])List[Double]
scala> abs(reals, imags)
res7: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)
编辑后: 好的,我已经开始做我想做的事情了。将获取任意类型数字的两个列表并返回一个双精度数组
def abs[A](r:List[A], im:List[A])(implicit numeric: Numeric[A]):Array[Double] = {
var t = new Array[Double](r.length)
for( i <- r.indices) {
t(i) = math.sqrt(numeric.toDouble(r(i))*numeric.toDouble(r(i))+numeric.toDouble(im(i))*numeric.toDouble(im(i)))
}
t
}
def abs[A](r:List[A],im:List[A])(隐式数字:数字[A]):数组[Double]={
var t=新数组[双精度](r.长度)
例如(在scala中执行泛型/性能原语实际上涉及两种相关机制,scala使用这两种机制来避免装箱/拆箱(例如,在java.lang.Integer
中包装int
,反之亦然):
@specialize
类型注释
- 对数组使用
清单
是一个注释,它告诉java编译器创建代码的“原始”版本(类似于C++模板,所以我被告知)。检查与(不是)相比的类型声明(它是专门的)。它被添加在<强> 2.8 < /强>中,意思是例如代码<代码> cc[int ] .map(f:int=int)执行
时,不会装箱任何int
s(当然,假设CC
是专用的!)
Manifest
s是在scala中执行具体化类型的一种方法(受JVM类型擦除的限制)。当您希望在某个类型上泛化方法T
,然后在方法中创建一个T
(即T[]
)数组时,这尤其有用。在Java中,这是不可能的,因为new T[]
是非法的。在scala中,这是可以使用清单的。特别是,在这种情况下,它允许我们构造一个基本的T型数组,如double[]
或int[]
(如果您想知道的话,这太棒了)
拳击从性能的角度来看是非常重要的,因为它创建了垃圾,除非你所有的<代码> int >代码> s都是127。显然,在额外的进程步骤/方法调用等方面增加了一个间接的级别。但是考虑到,除非你绝对确信你一定会这样做,否则你可能不会发出一个信号。(即,大多数代码不需要这种微观优化)
因此,回到问题上来:为了在不装箱/拆箱的情况下完成此操作,必须使用
数组(列表尚未专门化,并且无论如何都会更需要对象,即使是这样!)。一对集合上的压缩函数将返回Tuple2
的集合(不需要拳击,因为这是专门的)
为了一般地(即跨各种数字类型)执行此操作,您必须在您的通用参数上要求一个上下文绑定,即它是numeric
,并且可以找到Manifest
(创建数组所需)
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
}
…但它不太管用。原因是“通用”数值
值没有类似于sqrt
->的操作,因此您只能在知道自己有一个双精度
时执行此操作。例如:
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]
非常好-现在看看这个纯粹的通用方法,做一些事情吧
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
结果,因为我们有一个数组[Double]
scala> res0.map(math.sqrt(_))
res1: Array[Double] = Array(6.609841147864296, 5.481788029466298, 5.980802621722272)
为了证明即使使用另一种<代码>数值
类型,这也能起作用:
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)
Java中没有一种简单的方法来创建通用数值计算代码;从oxbow的回答中可以看出,库并不存在。集合也被设计为接受任意类型,这意味着使用原语时会有开销。因此,最快的代码(无需仔细检查边界)是:
def abs(re: Array[Double], im: Array[Double]) = {
val a = new Array[Double](re.length)
var i = 0
while (i < a.length) {
a(i) = math.sqrt(re(i)*re(i) + im(i)*im(i))
i += 1
}
a
}
将在简洁和概念上清晰地完成这个技巧(一旦你了解了zipped
的工作原理)。我认为这样做的代价是速度慢了大约2倍。(使用List
使其比while或tail递归慢7倍;使用zip
的List
使其慢20倍;使用数组的泛型即使不计算平方根也慢3倍。)
(编辑:固定计时以反映更典型的用例。)您可以使用简单的Math.sqrt()代替java.lang.Math.sqrt(),列表也不是专门化的,因此您的答案不会避免装箱“计算机科学中的所有问题都可以通过间接方式解决……除了这一个问题”;)下面的答案是几乎通用的解决方案。它失败了,因为没有一个代码> >代码> >代码>任意的代码>数字< /代码>类型感谢。特别有用的是学习这个压缩函数,也隐含地。不幸的是,Scala中的泛型看起来相当复杂。讽刺的是它似乎是C++。mplates更简单。我不太愿意问这个问题,因为我认为这可能是一个“愚蠢”的问题。事实证明,这其实相当困难,我学到了很多。我认为@specialize
值得一提。这似乎是最简单的方法,也是一个(罕见的?)案例中,它不如Java优雅。也感谢您的基准测试。有用的信息。@间接-是的,这是性能/优雅曲线走向优雅一侧的案例之一(至少最初是这样;希望最终我们会两者兼而有之)。有用。我不知道r.Index,我一直需要类似的东西。
def abs(re: Array[Double], im: Array[Double]) = {
val a = new Array[Double](re.length)
var i = 0
while (i < a.length) {
a(i) = math.sqrt(re(i)*re(i) + im(i)*im(i))
i += 1
}
a
}
def abs(re: Array[Double], im: Array[Double]) = {
def recurse(a: Array[Double], i: Int = 0): Array[Double] = {
if (i < a.length) {
a(i) = math.sqrt(re(i)*re(i) + im(i)*im(i))
recurse(a, i+1)
}
else a
}
recurse(new Array[Double](re.length))
}
def abs(re: Array[Double], im: Array[Double]) = {
(re,im).zipped.map((i,j) => math.sqrt(i*i + j*j))
}