Using Sequences – the equivalent to Java Streams.
// When chaining a e.g. filter and map together there will be intermediate Collections formed in the background. What if there are a large number of chained actions or the Collection is very large (or unknown size)?
// To avoid the creation of intermediate Collections we can use Sequences. Equivalent to Streams in Java, and aren't available on all platforms.
// Each element is evaluated and passed to next step in chain (if appropriate). No need for intermediates
// Only one function - iterator. Can convert a Collection to a Sequence but ONLY DO THIS FOR LARGE COLLECTIONS as Kotlin Collections are very efficient.
fun main() {
val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
2 to Car3("red", "Ford", 2016),
3 to Car3("silver", "Honda", 2013),
17 to Car3("red", "BMW", 2015),
8 to Car3("green", "Ford", 2010)
)
// Two types of Sequence - intermediate (returns a Sequence to pass onto next operation) and terminal (terminates the chain)
println(immutableMap.asSequence().filter { it.value.model == "Ford" }
.map {it.value.colour}) // Intermediate - lazy i.e. not evaluated immediately. If you never reach terminal operation would be wasteful to evaluate intermediates
listOf("Joe", "Mary", "Jane").asSequence()
.filter { println("Filtering $it"); it[0] == 'J' } // Intermediate operation
.map { println("Mapping $it"); it.toUpperCase() } // Intermediate operation. No return - need terminal operation
val name = listOf("Joe", "Mary", "Jane").asSequence()
.filter { println("Filtering $it"); it[0] == 'J' }
.map { println("Mapping $it"); it.toUpperCase() }
.toList() // Terminal operation - can now see results of operations
println(name)
// In some cases the desired result will be returned long before iterating the entire dataset, so many intermediate Collections could be avoided by using Sequences:
println("======")
val name1 = listOf("Joe", "Mary", "Jane").asSequence()
.filter { println("Filtering $it"); it[0] == 'J' }
.map { println("Mapping $it"); it.toUpperCase() }
.find { it.endsWith('E') } // Operation completes without ever having to evaluate Mary and Jane
println(name1)
println("======")
// With Collections, the order in which you call functions makes a difference. If we reverse the filter and map functions:
val name2 = listOf("Joe", "Mary", "Jane")
.map { println("Mapping $it"); it.toUpperCase() }
.filter { println("Filtering $it"); it[0] == 'J' }
.find { it.endsWith('E') } // All 3 names are mapped and then filtered - less efficient
println(name2)
println("======")
// But with Sequences, the order in which you call functions may or may not make a difference. If we reverse the filter and map functions:
val name3 = listOf("Joe", "Mary", "Jane").asSequence()
.map { println("Mapping $it"); it.toUpperCase() }
.filter { println("Filtering $it"); it[0] == 'J' }
.find { it.endsWith('E') } // No difference
println(name3)
println("======")
// But if we put Joe last:
val name4 = listOf("Mary", "Jane", "Joe").asSequence()
.map { println("Mapping $it"); it.toUpperCase() }
.filter { println("Filtering $it"); it[0] == 'J' }
.find { it.endsWith('E') } // Much less efficient
println(name4)
// Moral of the story: think out the order in which you run operations
}
