gpt4 book ai didi

Scala - 遍历两个数组

转载 作者:行者123 更新时间:2023-12-04 13:11:08 25 4
gpt4 key购买 nike

如何迭代两个相同大小的数组,每次迭代访问相同的索引 The Scala Way™?

      for ((aListItem, bListItem) <- (aList, bList)) {
// do something with items
}

Java方式应用于Scala:
     for(i <- 0 until aList.length ) {
aList(i)
bList(i)
}

假设两个列表的大小相同。

最佳答案

tl;dr :在速度和便利性之间需要权衡;您需要了解您的用例以进行适当的选择。

如果你知道两个数组的长度相同并且你不需要担心它有多快,最简单和最规范的方法是在 for-comprehension 中使用 zip:

for ((a,b) <- aList zip bList) { ??? }

然而, zip 方法会创建一个新的单个数组。为了避免这种开销,您可以在元组上使用 zipped ,该元组会将元素成对呈现给 foreachmap 之类的方法:
(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

当然,对于基元,这尤其糟糕! (如果您尝试使用 ArrayListInteger s 而不是 Java 中的 Arrayint,则所花费的时间也会有类似的巨大跳跃。)特别注意, 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)) { ??? }

在任何这些情况下,您都可以使用 yieldformap 而不是 foreach 来生成集合。

如果您需要计算的索引或者它确实是一个数组并且您确实需要它快速,您将不得不手动进行计算。填充缺失的元素很尴尬(我把它留给读者作为练习),但基本形式是:
for (i <- 0 until math.min(aList.length, bList.length)) { ??? }

然后使用 i 索引到 aListbList

如果您确实需要最大速度,您将再次使用(尾)递归或 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)

关于Scala - 遍历两个数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28335495/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com