【问题标题】:Scala - Iterate Over Two ArraysScala - 遍历两个数组
【发布时间】:2015-02-05 03:04:39
【问题描述】:

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

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

应用于Scala的Java方式:

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

假设两个列表大小相同。

【问题讨论】:

  • 你开始的东西是怎么回事?如果问题是它无法编译,只需在元组中添加定义:'for ((aListItem:Int, bListItem:Int)...'
  • Scala 方式 (TM) 不是使用数组(可变),而是使用列表,这些对于随机访问(即通过索引)效率低下。无论如何,无论您使用哪种,正确的解决方案都隐藏在 Rex Kerr 的答案中:(aList, bList).zipped.foreach{ (a,b) =&gt; ??? }。是的,它很有效,因为它不需要为每个元素创建元组(不像zip)。
  • 我更新了答案以包含时间信息。 @LuigiPlinge - 如果您的数组是 not 原语,则压缩是一种快速而简单的方法。如果是的话,拳击开销会杀死你。
  • 好点,谢谢@Rex。这仅适用于数组,不适用于列表或向量,因为后者的原语已经被装箱了,对吧?

标签: scala


【解决方案1】:

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

尤其很糟糕,当然,对于原语! (如果您尝试在 Java 中使用 ArrayLists 或 Integer 而不是 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)

【讨论】:

  • 请注意,zipped 现在已弃用。相反,应该使用lazyZip。像这样a.lazyZip(b).foreach{(a, b) =&gt; ??? }
【解决方案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) ... 

【讨论】:

  • 我认为与 Java 方式相比,添加 zip 例程会损害性能。
  • 必须查看源代码以查看 zip 是否包装或构造新数组。
  • @BAR 这可能有助于考虑性能,但又取决于用例和编译器优化:stackoverflow.com/questions/2794823/…
  • @BAR 它确实创建了一个中间体。
  • zip 创建一个新数组并复制数据。需要像通常的方式一样快的东西(通过保持索引)。
【解决方案3】:

我会这样做:

aList.indices foreach { i => 
   val (aListItem, bListItem) = (aList(i), bList(i))
   // do something with items
}

【讨论】:

  • 我非常喜欢这个。简洁的 Scala 语法和功能。
  • 如果bListaList 短,这将引发异常。
  • @RexKerr 两个列表的大小相同。更新帖子以澄清。
【解决方案4】:
for {
    i <- 0 until Math.min(list1.size, list2.size)
 } yield list1(i) + list2(i)

或类似的检查边界等。

【讨论】:

  • 它只会检查 list1 的边界。如果列表在大小上实际上没有同步,它仍然可能会爆炸
  • 这将总是超出范围,因为您使用了to,这是包容性的。
  • 我已经获得了需要检查边界的答案,但我已经为您更新了。
猜你喜欢
  • 2016-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 2017-04-08
  • 1970-01-01
  • 2013-10-04
  • 2010-10-04
相关资源
最近更新 更多