如何在scala中实现延迟序列(iterable)?

时间:2010-12-22 16:21:41

标签: scala yield

我想实现一个惰性迭代器,它在每个调用中产生下一个元素,在3级嵌套循环中。

scala中有类似于c#的这个片段:

foreach (int i in ...)
    {
        foreach (int j in ...)
        {
            foreach (int k in ...)
            {
                 yield return do(i,j,k);
            }
        }
    }

谢谢,Dudu

8 个答案:

答案 0 :(得分:6)

Scala序列类型都有一个.view方法,它生成一个惰性的集合。您可以在REPL中使用以下内容(在发出:silent之后阻止它强制集合打印命令结果):

def log[A](a: A) = { println(a); a } 

for (i <- 1 to 10) yield log(i)

for (i <- (1 to 10) view) yield log(i)

第一个将打印出数字1到10,第二个将打印出来,直到你真正尝试访问结果的那些元素。

Scala中没有任何内容直接等同于C#的yield语句,它会暂停循环的执行。您可以使用为scala 2.8添加的delimited continuations获得类似的效果。

答案 1 :(得分:4)

如果将迭代器与++一起加入,则会得到一个遍历两者的迭代器。 reduceLeft方法有助于将整个集合连接在一起。因此,

def doIt(i: Int, j: Int, k: Int) = i+j+k
(1 to 2).map(i => {
  (1 to 2).map(j => {
    (1 to 2).iterator.map(k => doIt(i,j,k))
  }).reduceLeft(_ ++ _)
}).reduceLeft(_ ++ _)

将生成您想要的迭代器。如果您希望它比这更懒,您也可以在前两个.iterator之后添加(1 to 2)。 (当然,将每个(1 to 2)替换为您自己更有趣的集合或范围。)

答案 2 :(得分:2)

您可以使用Sequence Comprehension而不是Iterators来获得所需内容:

for {
    i <- (1 to 10).iterator
    j <- (1 to 10).iterator
    k <- (1 to 10).iterator
} yield doFunc(i, j, k)

如果你想创建一个懒惰的Iterable(而不是一个懒惰的Iterator),请使用Views代替:

for {
    i <- (1 to 10).view
    j <- (1 to 10).view
    k <- (1 to 10).view
} yield doFunc(i, j, k)

根据你想要的懒惰程度,你可能不需要调用迭代器/视图。

答案 3 :(得分:1)

如果您的3个迭代器通常很小(即,您可以完全迭代它们而不用考虑内存或CPU)并且昂贵的部分是计算给定i,j和k的结果,则可以使用Scala的Stream类。

val tuples = for (i <- 1 to 3; j <- 1 to 3; k <- 1 to 3) yield (i, j, k)
val stream = Stream(tuples: _*) map { case (i, j, k) => i + j + k }
stream take 10 foreach println

如果你的迭代器对于这种方法来说太大了,你可以扩展这个想法并创建一个元组流,通过保持每个迭代器的状态来懒惰地计算下一个值。例如(虽然希望有人有更好的方法来定义产品方法):

def product[A, B, C](a: Iterable[A], b: Iterable[B], c: Iterable[C]): Iterator[(A, B, C)] = {
  if (a.isEmpty || b.isEmpty || c.isEmpty) Iterator.empty
  else new Iterator[(A, B, C)] {
    private val aItr = a.iterator
    private var bItr = b.iterator
    private var cItr = c.iterator

    private var aValue: Option[A] = if (aItr.hasNext) Some(aItr.next) else None
    private var bValue: Option[B] = if (bItr.hasNext) Some(bItr.next) else None

    override def hasNext = cItr.hasNext || bItr.hasNext || aItr.hasNext
    override def next = {
      if (cItr.hasNext)
        (aValue get, bValue get, cItr.next)
      else {
        cItr = c.iterator
        if (bItr.hasNext) {
          bValue = Some(bItr.next)
          (aValue get, bValue get, cItr.next)
        } else {
          aValue = Some(aItr.next)
          bItr = b.iterator
          (aValue get, bValue get, cItr.next)
        }
      }
    }
  }
}

val stream = product(1 to 3, 1 to 3, 1 to 3).toStream map { case (i, j, k) => i + j + k }
stream take 10 foreach println

这种方法完全支持无限大小的输入。

答案 4 :(得分:1)

我认为下面的代码就是你真正想要的......我认为编译器最终将它转换为Rex给出的等效地图代码,但更接近原始示例的语法:

scala> def doIt(i:Int, j:Int) = { println(i + ","+j); (i,j); }
doIt: (i: Int, j: Int)(Int, Int)

scala> def x = for( i <- (1 to 5).iterator; 
                    j <- (1 to 5).iterator ) yield doIt(i,j)
x: Iterator[(Int, Int)]

scala> x.foreach(print)
1,1
(1,1)1,2
(1,2)1,3
(1,3)1,4
(1,4)1,5
(1,5)2,1
(2,1)2,2
(2,2)2,3
(2,3)2,4
(2,4)2,5
(2,5)3,1
(3,1)3,2
(3,2)3,3
(3,3)3,4
(3,4)3,5
(3,5)4,1
(4,1)4,2
(4,2)4,3
(4,3)4,4
(4,4)4,5
(4,5)5,1
(5,1)5,2
(5,2)5,3
(5,3)5,4
(5,4)5,5
(5,5)
scala> 

您可以从输出中看到,在迭代x的下一个值之前,不会调用“doIt”中的print,并且这种类型的for generator比一堆嵌套映射更容易读/写

答案 5 :(得分:0)

只需阅读旁边显示的20个左右第一个相关链接(实际上,当您第一次写出问题标题时,显示给您的位置)。

答案 6 :(得分:0)

将问题颠倒过来。将“do”作为闭包传递。这就是使用函数式语言的全部意义

答案 7 :(得分:0)

Iterator.zip会这样做:

iterator1.zip(iterator2).zip(iterator3).map(tuple => doSomething(tuple))