10

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 this snippet of c#:

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

Thanks, Dudu

2
  • BTW - if there's a similar way of doing so in JAVA or any JVM language - i'll also be glad to hear that. Commented Dec 22, 2010 at 16:24
  • 1
    possible duplicate of Does Scala have an equivalent to C# yield? Commented Dec 22, 2010 at 17:57

8 Answers 8

8

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.

Sign up to request clarification or add additional context in comments.

Comments

6

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.)

1 Comment

That sounds like the best option I've got, still not exactly as C#'s yield but it's pretty good. Thanks!
4

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.

Comments

1

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.

Comments

1

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.

Comments

0

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

Comments

0

Iterator.zip will do it:

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

1 Comment

No, this does not produce the cartesian product that a nested loop would.
-1

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).

2 Comments

I've read the links thoroughly - neither of them showed exactly what I was looking for, and it seems like it doesn't exist at all... C#'s yield is more powerful (at least for what I need). Also, I thought that it was possible that this functionality was added to scala after these previous questions were asked.
@duduamar Aside the one implemented through continuations, there isn't something equivalent to C#'s yield, which was pretty much my point. This question has been asked before.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.