如何迭代两个相同大小的数组,每次迭代访问相同的索引Scala Way™?
for ((aListItem, bListItem) <- (aList, bList)) {
// do something with items
}
应用于Scala的Java方法:
for(i <- 0 until aList.length ) {
aList(i)
bList(i)
}
假设两个列表的大小相同。
答案 0 :(得分:13)
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 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中使用ArrayList
Integer
而不是Array
int
,则会获得类似的大幅跳跃。)特别注意{{1}如果您存储了对象,那么这是一个非常合理的选择。
但要注意过早优化!像zipped
这样的功能形式在清晰度和安全性方面具有优势。如果你总是写while循环,因为你认为“每一点点都有帮助”,你可能会犯一个错误,因为它需要更多的时间来编写和调试,你可以利用那段时间来优化程序中一些更重要的部分。 / p>
但是,假设您的阵列长度相同是危险的。你确定吗?您需要付出多少努力才能确定?也许你不应该做出这样的假设?
如果您不需要快速,只需更正,那么如果两个数组的长度不同,您必须选择该怎么做。
如果你想对所有元素做一些长度较短的元素,那么zip
仍然是你使用的:
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
。
如果真的需要最高速度,你会再次使用(尾部)递归或while循环:
bList
答案 1 :(得分:2)
类似的东西:
for ((aListItem, bListItem) <- (aList zip bList)) {
// do something with items
}
或map
喜欢:
(aList zip bList).map{ case (alistItem, blistItem) => // do something }
<强>更新强>
对于不创建中间体的迭代,您可以尝试:
for (i <- 0 until xs.length) ... //xs(i) & ys(i) to access element
或只是
for (i <- xs.indices) ...
答案 2 :(得分:1)
for {
i <- 0 until Math.min(list1.size, list2.size)
} yield list1(i) + list2(i)
或类似检查边界等的东西
答案 3 :(得分:1)
我会做这样的事情:
aList.indices foreach { i =>
val (aListItem, bListItem) = (aList(i), bList(i))
// do something with items
}