我想实现一个惰性迭代器,它在每个调用中产生下一个元素,在3级嵌套循环中。
scala中有类似于c#的这个片段:
foreach (int i in ...)
{
foreach (int j in ...)
{
foreach (int k in ...)
{
yield return do(i,j,k);
}
}
}
谢谢,Dudu
答案 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))