Sequences
Sequence allows to perform all the processing steps one-by-one for every single element.
The sequence itself does not store elements.
You can use the same operations on a sequence as on a collection, such as map, filter, etc.
There are two types of operations:
- intermediate operations - define processing steps. In fact, each step is a sequence with a predicate to be used in the traversal. For example, map() and filter() are intermediate operations.
- terminal operations - define last processing step. All intermediate operations will be invoked for each element. As a result, the operation may produce a single value or a new collection. Obviously, only one terminal operation can be used. For example, count() and toList() are terminal operations.
Sequence is an alternative to Java stream.
fun fibonacci() = sequence {
var terms = Pair(0, 1)
// this sequence is infinite
while (true) {
yield(terms.first)
terms = Pair(terms.second, terms.first + terms.second)
}
}
println(fibonacci().take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
creation
You can create sequence from any Iterable or Iteratorobject.
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
val numbersSequence2 = (1..10).asSequence()
val result = myIterator.asSequence().filter {
// todo
}.filter {
// todo
}.toList()
You can create sequence from elements.
val numbersSequence = sequenceOf("four", "three", "two", "one")
You can generate sequence elements with a given function. It looks like math sequence.
val oddNumbers = generateSequence(1) { it + 2 } // `it` is the previous element
println(oddNumbers.take(5).toList())
//println(oddNumbers.count()) // error: the sequence is infinite
You can create sequence from chunks.
val oddNumbers = sequence {
yield(1)
yieldAll(listOf(3, 5))
yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())
sequence vs collections
Sequences let you avoid building results of intermediate steps, therefore improving the performance of the whole collection processing chain.
dstCollection = srcCollection
.filter { /* ... */ } // new collection will be created
.map { /* ... */ } // new collection will be created
.filter { /* ... */ } // new collection will be created
dstCollection = srcCollection
.asSequence()
.filter { /* ... */ } // declare step
.map { /* ... */ } // declare step
.filter { /* ... */ } // declare step
.toList() // new collection will be created
sequence vs Java stream
Sequence advantages:
- available on all Kotlin platforms, not only JVM
- most terminal operations are inline
- more specified operations available like mapNotNull
- built-in aggregation operations like groupBy more concise
- the Kotlin null safety features are used. Java uses Optional type to wrap null values.
Stream advantages:
- have primitive variants to avoid unnecessary autoboxing
- enables easy parallelism via parallel streams