In Kotlin, we can also make use of lazy evaluation using Sequences, too. In order to create a sequence, we may use generateSequence
(with or without providing a seed
.
fun <T : Any> generateSequence(
seed: T?,
nextFunction: (T) -> T?
): Sequence<T> (source)
Returns a sequence defined by the starting value seed
and the function nextFunction
, which is invoked to calculate the next value based on the previous one on each iteration.
The following will show some examples comparing Clojure with Kotlin sequences.
1. A simple take
from an infinite sequence of one static value
Clojure
(take 3 (repeat "Hello StackOverflow"))
Kotlin
generateSequence { "Hello StackOverflow" }.take(3).toList()
These are pretty similar. In Clojure we can use repeat
and in Kotlin it's simply generateSequence
with a static value that will be yielded for ever. In both cases, take
is being used in order to define the number of elements we want to compute.
Note: In Kotlin, we transform the resulting sequence into a list with toList()
2. A simple take
from an infinite sequence of an dynamic value
Clojure
(take 5 (iterate inc 1))
Kotlin
generateSequence(1) { it.inc() }.take(5).toList()
This example is a bit different because the sequences yield the increment of the previous value infinitely. The Kotlin generateSequence
can be invoked with a seed (here: 1
) and a nextFunction
(incrementing the previous value).
3. A cyclic repetition of values from a list
Clojure
(take 5 (drop 2 (cycle [:first :second :third ])))
// (:third :first :second :third :first)
Kotlin
listOf("first", "second", "third").let { elements ->
generateSequence(0) {
(it + 1) % elements.size
}.map(elements::get)
}.drop(2).take(5).toList()
In this example, we repeat the values of a list cyclically, drop the first two elements and then take 5. It happens to be quite verbose in Kotlin because repeating elements from the list isn't straightforward. In order to fix it, a simple extension function makes the relevant code more readable:
fun <T> List<T>.cyclicSequence() = generateSequence(0) {
(it + 1) % this.size
}.map(::get)
listOf("first", "second", "third").cyclicSequence().drop(2).take(5).toList()
4. Factorial
Last but not least, let's see how the factorial problem can be solved with a Kotlin sequence. First, let's review the Clojure version:
Clojure
(defn factorial [n]
(apply * (take n (iterate inc 1))))
We take n values from a sequence that yields an incrementing number starting with 1 and accumulate them with the help of apply
.
Kotlin
fun factorial(n: Int) = generateSequence(1) { it.inc() }.take(n).fold(1) { v1, v2 ->
v1 * v2
}
Kotlin offers fold
which let's us accumulate the values easily.
buildSequence { val items = listOf("first", "second", "third"); var i = 0; while (true) { yield(items[i++]); i %= items.size } }
– Devitalize