Stream offers is
Stream.grouped, which divides a stream’s elements into groups of a given size.
To take an example, if you have a stream of twelve elements and call
Stream.grouped to turn it into groups of three, you’ll get an iterator over four sequences, each three elements in size:
val xs = (1 to 12).toStream val grouped = xs.grouped(3) // grouped: Iterator[Stream[Int]] = // Iterator( // Stream(1, 2, 3), Stream(4, 5, 6), // Stream(7, 8, 9), Stream(10, 11, 12))
What happens if you use a group size that does not divide evenly into the size of your input stream? No sweat! The last group just contains any remaining elements, however many they may be:
val xs = (1 to 10).toStream val grouped = xs.grouped(3) // grouped: Iterator[Stream[Int]] = // Iterator( // Stream(1, 2, 3), Stream(4, 5, 6), // Stream(7, 8, 9), Stream(10))
Where is this useful? Well, you can take my paging example from my Scala Saturday post on
Stream.drop and make it slightly clearer without the
(page - 1) * perPage arithmetic:
case class Book(title: String, author: String) val books = Stream( Book("Wuthering Heights", "Emily Bronte"), Book("Jane Eyre", "Charlotte Bronte"), Book("Agnes Grey", "Anne Bronte"), Book("The Scarlet Letter", "Nathaniel Hawthorne"), Book("Silas Marner", "George Eliot"), Book("1984", "George Orwell"), Book("Billy Budd", "Herman Melville"), Book("Moby Dick", "Herman Melville"), Book("The Great Gatsby", "F. Scott Fitzgerald"), Book("Tom Sawyer", "Mark Twain") ) val perPage = 3 val page = 3 val records = books.grouped(perPage) .drop(page - 1) .next // records: scala.collection.immutable.Stream[Book] = // Stream(Book(Billy Budd,Herman Melville), // Book(Moby Dick,Herman Melville), // Book(The Great Gatsby,F. Scott Fitzgerald))
This time, instead of having to calculate the number of elements to skip in order to skip n pages, you first use
Stream.grouped to turn the stream into a paged recordset; each “page” is n records long. Then drop
page - 1 pages in order to get to the page of records you want. Finally, calling
Iterator.next is necessary because, remember,
Stream.grouped turns a flat stream into a stream of streams.
I will admit that I find it irritating that
Stream.grouped returns something that does not have a
head method. Calling
Iterator.next, while just as easy, is inconsistent with collection semantics. It seems to me that
Stream.grouped ought to return a collection rather than an iterator. Perhaps there was once a reason for returning an iterator instead of a collection, but it would be nice if we could fix that.