简而言之: 在速度和便利性之间需要做出权衡;你需要了解你的使用情况来进行适当的选择。
如果您知道两个数组具有相同的长度,并且不需要担心它的速度,最简单和最常规的方法是在for循环中使用zip
:
for ((a,b) <- aList zip bList) { ??? }
zip
方法创建了一个新的单一数组。但是,为了避免这种开销,您可以在元组上使用 zipped
,它会将元素成对呈现给像 foreach
和 map
这样的方法:
(aList, bList).zipped.foreach{ (a,b) => ??? }
更快的做法是索引到数组中,特别是如果数组包含像
Int
这样的基本类型,因为上面的通用代码必须将它们装箱。有一个方便的方法
indices
可以使用:
for (i <- aList.indices) { ??? }
最后,如果您需要尽可能快地进行操作,您可以退回到手动 while 循环或递归,如下所示:
var i = 0
while (i < aList.length) {
???
i += 1
}
def loop(i: Int) {
if (i < aList.length) {
???
loop(i+1)
}
}
loop(0)
如果您正在计算某个值,而不希望它产生副作用,使用递归时将其传递通常会更快:
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
100
185
2100
3700
当然,如果你使用原始数据类型,这种情况尤其糟糕!(如果你在 Java 中尝试使用包含 Integer 的 ArrayList 而不是 int 的 Array,时间消耗也会出现类似的巨大跳跃。)请注意,如果存储了对象,则使用“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)) { ??? }
在任何这些情况下,您可以使用for
或map
与yield
代替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)
(aList, bList).zipped.foreach{ (a,b) => ??? }
。是的,它很高效,因为它不需要为每个元素创建元组(与zip
不同)。 - Luigi Plinge