新手Scala关于简单数学数组操作的问题

新手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:

新手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

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))
}