tl;dr:速度和便利性之间需要权衡;您需要了解您的用例才能做出适当的选择。
如果您知道两个数组的长度相同并且无需担心它的速度有多快,那么最简单且最规范的方法是使用zip
在 for 理解中:
for ((a,b) <- aList zip bList) { ??? }
The zip
然而,方法创建一个新的单个数组。为了避免这种开销,您可以使用zipped
在一个元组上,它将成对地向方法呈现元素,例如foreach
and map
:
(aList, bList).zipped.foreach{ (a,b) => ??? }
更快的方法是对数组进行索引,特别是当数组包含像这样的基元时Int
,因为上面的通用代码必须将它们装箱。有一个方便的方法indices
您可以使用:
for (i <- aList.indices) { ??? }
最后,如果您需要尽可能快地进行,您可以退回到手动 while 循环或递归,如下所示:
// While loop
var i = 0
while (i < aList.length) {
???
i += 1
}
// Recursion
def loop(i: Int) {
if (i < aList.length) {
???
loop(i+1)
}
}
loop(0)
如果您正在计算某个值,而不是让它成为副作用,那么如果您传递它,有时递归会更快:
// Recursion with explicit result
def loop(i: Int, acc: Int = 0): Int =
if (i < aList.length) {
val nextAcc = ???
loop(i+1, nextAcc)
}
else acc
由于您可以在任何地方删除方法定义,因此您可以不受限制地使用递归。您可以添加一个@annotation.tailrec
注释以确保它可以编译为带有跳转的快速循环,而不是占用堆栈空间的实际递归。
采用所有这些不同的方法来计算长度为 1024 的向量的点积,我们可以将它们与 Java 中的参考实现进行比较:
public class DotProd {
public static int dot(int[] a, int[] b) {
int s = 0;
for (int i = 0; i < a.length; i++) s += a[i]*b[i];
return s;
}
}
加上一个等效版本,我们取字符串长度的点积(这样我们就可以评估对象与基元)
normalized time
-----------------
primitive object method
--------- ------ ---------------------------------
100% 100% Java indexed for loop (reference)
100% 100% Scala while loop
100% 100% Scala recursion (either way)
185% 135% Scala for comprehension on indices
2100% 130% Scala zipped
3700% 800% Scala zip
This is 特别当然,对于原语来说是不好的! (如果你尝试使用,你会得到同样巨大的时间跳跃ArrayList
s of Integer
代替Array
of int
在 Java 中。)特别注意zipped
如果您存储了对象,这是一个相当合理的选择。
不过,请注意过早的优化!功能形式在清晰度和安全性方面具有优势,例如zip
。如果您总是因为认为“每一点都有帮助”而编写 while 循环,那么您可能会犯一个错误,因为它需要更多时间来编写和调试,并且您可以利用这些时间来优化程序中一些更重要的部分。
但是,假设数组的长度相同是危险的。你是sure?你会付出多少努力来确定?也许你不应该做出这样的假设?
如果你不需要它快,只需要正确,那么你必须选择如果两个数组长度不同时要做什么。
如果您想对所有元素执行某些操作,直到较短的长度,那么zip
仍然是你使用的:
// The second is just shorthand for the first
(aList zip bList).foreach{ case (a,b) => ??? }
for ((a,b) <- (aList zip bList)) { ??? }
// This avoids an intermediate array
(aList, bList).zipped.foreach{ (a,b) => ??? }
如果您想用默认值填充较短的一个,您可以
aList.zipAll(bList, aDefault, bDefault).foreach{ case (a,b) => ??? }
for ((a,b) <- aList.zipAll(bList, aDefault, bDefault)) { ??? }
在任何这些情况下,您都可以使用yield
with for
or map
代替foreach
生成一个集合。
如果您需要索引进行计算,或者它确实是一个数组并且您确实需要它快速,则必须手动进行计算。填充缺失的元素很尴尬(我将其作为练习留给读者),但基本形式是:
for (i <- 0 until math.min(aList.length, bList.length)) { ??? }
然后你在哪里使用i
索引到aList
and bList
.
If you really需要最大速度,您将再次使用(尾)递归或 while 循环:
val n = math.min(aList.length, bList.length)
var i = 0
while (i < n) {
???
i += 1
}
def loop(i: Int) {
if (i < aList.length && i < bList.length) {
???
loop(i+1)
}
}
loop(0)