Effective Kotlin - Prefer Sequence for big collections with more than one processing step

Summary

Eager evaluation vs Lazy evaluation

Order is important

The result of an iterable structure like listOf is different from that of sequenceOf.


sequenceOf(1,2,3)
    .filter { print("F$it, "); it % 2 == 1 }
    .map { print("M$it, "); it * 2 }
    .forEach { print("E$it,")}

// Prints: F1, M1, E2, F2, F3, M3, E6,

listOf(1,2,3)
       .filter { print("F$it, "); it % 2 == 1 }
       .map { print("M$it, "); it * 2 }
       .forEach { print("E$it, ") }

// Prints: F1, F2, F3, M1, M3, E2, E6,

Code Test

Sequences do the minimal number of operations

They perform the minimum amount of work necessary.


(1..10).asSequence()
   .filter { print("F$it, "); it % 2 == 1 }
   .map { print("M$it, "); it * 2 }
   .find { it > 5 }
// Prints: F1, M1, F2, F3, M3,

(1..10)
   .filter { print("F$it, "); it % 2 == 1 }
   .map { print("M$it, "); it * 2 }
   .find { it > 5 }
// Prints: F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, M1, M3, M5, M7, M9,

Code Test

Sequences can be infinite

Sequences can be infinite.


val fibonacci = sequence {
   yield(1)
   var current = 1
   var prev = 1
   while (true) {
       yield(current)
       val temp = prev
       prev = current
       current += temp
   }
}
print(fibonacci.take(10).toList())
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

print(fibonacci.toList())
// Runs forever

Code Test

Sequences do not create collections at every processing step


numbers
   .filter { it % 10 == 0 } // 1 collection here
   .map { it * 2 } // 1 collection here
   .sum()
// In total, 2 collections created under the hood
numbers
   .asSequence()
   .filter { it % 10 == 0 }
   .map { it * 2 }
   .sum()
// No collections created

When aren't sequences faster?

It is said that the sorted function is currently the only such case.
Caution: Be careful, as processing sorted on an infinite Sequence can lead to an infinite loop.


generateSequence(0) { it + 1 }.take(10).sorted().toList()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
generateSequence(0) { it + 1 }.sorted().take(10).toList()
// Infinite time. Does not return.

Conclusion

When dealing with large collections and performing more than one processing step, you should use Sequences.

AD