tl;dr:在速度和便利性之间进行权衡;您需要了解您的用例才能正确选择。
如果您知道两个数组的长度相同并且您不必担心它有多快,那么最简单和最规范的方法是在 for-comprehension 中使用 zip:
for ((a,b) <- aList zip bList) { ??? }
不过,zip 方法会创建一个新的单个数组。为了避免这种开销,您可以在元组上使用zipped,它将成对地呈现元素给foreach和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
这尤其很糟糕,当然,对于原语! (如果您尝试在 Java 中使用 ArrayLists 或 Integer 而不是 Array 或 int ,您会获得类似的巨大时间跳跃。)请特别注意,如果您有 zipped 是一个相当合理的选择存储的对象。
不过,请注意过早的优化!像zip 这样的功能形式在清晰和安全方面具有优势。如果您总是因为认为“每一点都有帮助”而编写 while 循环,那么您可能会犯错误,因为编写和调试需要更多时间,您可能会利用这段时间优化程序中更重要的部分。
但是,假设您的数组长度相同是危险的。你确定吗?你要付出多少努力才能确定?也许你不应该做出这样的假设?
如果你不需要它很快,只要正确,那么你必须选择如果两个数组长度不同时该怎么做。
如果你想对所有元素做一些事情,直到较短的长度,那么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 和 for 或 map 而不是 foreach 来生成集合。
如果您需要索引进行计算或者它确实是一个数组并且您确实需要它快速,您将不得不手动进行计算。填充缺失的元素很尴尬(我把它作为练习留给读者),但基本形式是:
for (i <- 0 until math.min(aList.length, bList.length)) { ??? }
然后使用i 索引到aList 和bList。
如果您真的需要最大速度,您将再次使用(尾)递归或 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)