Scala - 迭代两个数组

2024-01-08

如何迭代两个相同大小的数组,每次迭代访问相同的索引 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:速度和便利性之间需要权衡;您需要了解您的用例才能做出适当的选择。


如果您知道两个数组的长度相同并且无需担心它的速度有多快,那么最简单且最规范的方法是使用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 特别当然,对于原语来说是不好的! (如果你尝试使用,你会得到同样巨大的时间跳跃ArrayLists 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)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala - 迭代两个数组 的相关文章

随机推荐