In Scala, is there a way to get the currently evaluated items in a Stream?
In Scala, is there a way to get the currently evaluated items in a Stream? For example in the Stream
val s: Stream[Int] = Stream.cons(1, Stream.cons(2, Stream.cons(3, s.map(_+1))))
the method should return only List(1,2,3)
.
In 2.8, there is a protected method called tailDefined
that will return false when you get to the point in the stream that has not yet been evaluated.
This isn't too useful (unless you want to write your own Stream
class) except that Cons
itself makes the method public. I'm not sure why it's protected in Stream and not in Cons--I would think one or the other might be a bug. But for now, at least, you can write a method like so (writing a functional equivalent is left as an exercise to the reader):
def streamEvalLen[T](s: Stream[T]) = {
if (s.isEmpty) 0
else {
var i = 1
var t = s
while (t match {
case c: Stream.Cons[_] => c.tailDefined
case _ => false
}) {
i += 1
t = t.tail
}
i
}
}
Here you can see it in action:
scala> val s = Stream.iterate(0)(_+1)
s: scala.collection.immutable.Stream[Int] = Stream(0, ?)
scala> streamEvalLen(s)
res0: Int = 1
scala> s.take(3).toList
res1: List[Int] = List(0, 1, 2)
scala> s
res2: scala.collection.immutable.Stream[Int] = Stream(0, 1, 2, ?)
scala> streamEvalLen(s)
res3: Int = 3
The solution based on Rex's answer:
def evaluatedItems[T](stream: => Stream[T]): List[T] = {
@tailrec
def inner(s: => Stream[T], acc: List[T]): List[T] = s match {
case Empty => acc
case c: Cons[T] => if (c.tailDefined) {
inner(c.tail, acc ++ List(c.head))
} else { acc ++ List(c.head) }
}
inner(stream, List())
}
Type that statement into the interactive shell and you will see that it evaluates to s: Stream[Int] = Stream(1, ?)
. So, in fact, the other two elements of 2 and 3 are not yet known.
As you access further elements, more of the stream is calculated. So, now put s(3)
into the shell, which will return res0: Int = 2
. Now put s
into the shell and you will see the new value res1: Stream[Int] = Stream(1, 2, 3, 2, ?)
.
The only method I could find that contained the information that you wanted was, unfortunately, s.toString
. With some parsing you will be able to get the elements back out of the string. This is a barely acceptable solution with just ints and I couldn't imagine any generic solution using the string parsing idea.
Using scanLeft
lazy val s: Stream[Int] = 1 #:: s.scanLeft(2) { case (a, _) => 1 + a }
精彩评论