Scala for循环和迭代器

时间:2014-12-10 15:25:36

标签: scala loops iterator

假设我有一个非常大的可迭代值集合(大约100,000个字符串条目,逐个从磁盘读取),我在其笛卡尔积上做了一些事情(并将结果写回磁盘,虽然我不会在这里显示):

for(v1 <- values; v2 <- values) yield ((v1, v2), 1)

我知道这只是另一种写作方式

values.flatMap(v1 => values.map(v2 => ((v1, v2), 1)))

这显然会导致每个flatMap迭代(甚至整个笛卡尔积?)的整个集合保存在内存中。如果你使用for循环读取第一个版本,这显然是不必要的。理想情况下,只有两个条目(被组合的条目)应始终保存在内存中。

如果我重新制定第一个版本:

for(v1 <- values.iterator; v2 <- values.iterator) yield ((v1, v2), 1)

内存消耗低很多,这使我认为这个版本必须根本不同。它在第二个版本中的确有何不同?为什么Scala不会隐式使用第一个版本的迭代器?在某些情况下不使用迭代器时是否有任何加速?

谢谢! (还要感谢&#34; lmm&#34;谁回答了这个问题的早期版本)

2 个答案:

答案 0 :(得分:7)

在Scala中,yield不会生成延迟序列。我的理解是,您可以一次获取所有值,以便将它们全部索引为集合。例如,我为光线跟踪器编写了以下内容来生成光线:

def viewRays(aa:ViewHelper.AntiAliasGenerator) =
{
  for (y <- 0 until height; x <- 0 until width)
    yield (x, y, aa((x, y)))
}

失败了(失去记忆),因为它让所有的光线都在前面(惊喜!)。通过使用.iterator方法,您可以专门请求一个惰性迭代器。上面的例子可以修改为:

def viewRays(aa:ViewHelper.AntiAliasGenerator) =
{
  for (y <- 0 until height iterator; x <- 0 until width iterator)
    yield (x, y, aa((x, y)))
}

以懒惰的方式工作。

答案 1 :(得分:5)

严格评估第一个版本;它创建了一个真实的,具体的集合,包含所有这些值。第二个&#34;只是&#34;提供Iterator,让您迭代所有值;它们会在您实际执行迭代时创建。

Scala默认为第一个的主要原因是因为scala作为一种语言允许副作用。如果您将两个映射写为:

for(v1 <- values; v2 <- values) yield {println("hello"); ((v1, v2), 1)}
for(v1 <- values.iterator; v2 <- values.iterator) yield {
  println("hello"); ((v1, v2), 1)}
然后第二个会发生什么可能会让你感到惊讶,特别是在一个更大的应用程序中,迭代器可能会远离它实际使用的位置。

如果映射操作本身很昂贵并且您创建一次并重复使用多次,则集合将比迭代器执行得更好 - 迭代器每次都必须重新计算值,而集合存在于内存中。可以说这使得集合性能更具可预测性 - 它消耗了大量内存,但无论使用何种集合,它的数量都相同。

如果您想要一个更愿意忽略操作和优化的集合库 - 可能是因为您已经编写了所有代码而没有副作用 - 您可能需要考虑Paul Philips' new effort