Scala - 迭代两个数组

时间:2015-02-05 03:04:40

标签: scala

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

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

应用于Scala的Java方法:

     for(i <- 0 until aList.length ) {
          aList(i)
          bList(i)
      }

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

4 个答案:

答案 0 :(得分:13)

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 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)) { ??? } yieldfor代替map来生成集合。

如果您需要计算索引或者它确实是一个数组并且您确实需要快速,则必须手动进行计算。填充缺失的元素很尴尬(我把它作为练习留给读者),但基本形式将是:

foreach

然后您可以使用for (i <- 0 until math.min(aList.length, bList.length)) { ??? } 索引iaList

如果真的需要最高速度,你会再次使用(尾部)递归或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
}