开发者

How to implement lazy sequence (iterable) in scala?

开发者 https://www.devze.com 2023-02-01 00:34 出处:网络
I want to implement a lazy iterator that yields the next element in each call, in a 3-level nested loop.

I want to implement a lazy iterator that yields the next element in each call, in a 3-level nested loop.

Is there something similar in scala to t开发者_JS百科his snippet of c#:

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

Thanks, Dudu


Scala sequence types all have a .view method which produces a lazy equivalent of the collection. You can play around with the following in the REPL (after issuing :silent to stop it from forcing the collection to print command results):

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)

The first will print out the numbers 1 to 10, the second will not until you actually try to access those elements of the result.

There is nothing in Scala directly equivalent to C#'s yield statement, which pauses the execution of a loop. You can achieve similar effects with the delimited continuations which were added for scala 2.8.


If you join iterators together with ++, you get a single iterator that runs over both. And the reduceLeft method helpfully joins together an entire collection. Thus,

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(_ ++ _)

will produce the iterator you want. If you want it to be even more lazy than that, you can add .iterator after the first two (1 to 2) also. (Replace each (1 to 2) with your own more interesting collection or range, of course.)


You can use a Sequence Comprehension over Iterators to get what you want:

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

If you want to create a lazy Iterable (instead of a lazy Iterator) use Views instead:

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

Depending on how lazy you want to be, you may not need all of the calls to iterator / view.


If your 3 iterators are generally small (i.e., you can fully iterate them without concern for memory or CPU) and the expensive part is computing the result given i, j, and k, you can use Scala's Stream class.

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

If your iterators are too large for this approach, you could extend this idea and create a Stream of tuples that calculates the next value lazily by keeping state for each iterator. For example (although hopefully someone has a nicer way of defining the product method):

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

This approach fully supports infinitely sized inputs.


I think the below code is what you're actually looking for... I think the compiler ends up translating it into the equivalent of the map code Rex gave, but is closer to the syntax of your original example:

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> 

You can see from the output that the print in "doIt" isn't called until the next value of x is iterated over, and this style of for generator is a bit simpler to read/write than a bunch of nested maps.


Turn the problem upside down. Pass "do" in as a closure. That's the entire point of using a functional language


Iterator.zip will do it:

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


Just read the 20 or so first related links that are show on the side (and, indeed, where shown to you when you first wrote the title of your question).

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号