I'm coming from Java and I have a question about w...
# getting-started
e
I'm coming from Java and I have a question about when to use
asSequence()
. In a lot of Advent of Code solutions, I'm seeing people use maps and filters on lists directly which Kotlin allows you to do but in Java we'd be forced to use Streams in all cases. Is there a reason to not use sequences in most cases? Is there so much overhead to convert to sequences that makes this not worthwhile for lists of a few hundred elements?
s
👍 2
👆🏻 1
d
Nice article! I will say though, it's missed a little bit of the subtlety of the
.toList()
runtime complexity:
MutableList.add()
has an O(1) amortized complexity, so for large enough sequences,
toList()
is relatively similar in performance as if you were using a List instead.
s
Interesting point! Since the extra array copying probably adds an amortized constant factor of 2 or 3, that means we can say that regardless of the number of items, the extra cost of the toList operation on a sequence is equivalent to one or two extra intermediate operators when not using sequences 💡blob think smart.
d
Yep, basically. Of course, the advice in the article that is the best is "Profile before deciding"
The other situation where I would consider it important to use sequence over others is when any one of the intermediate operations may make use of external resources, such as making HTTP or database calls.
That way, if there is a short circuit later on, you won't have unnecessary calls.
k
It's more than just a performance consideration. Sometimes, using a collection directly gives different results from using sequences. Consider this contrived example, to get a distinct list (you wouldn't do it like this in real code; this is just for illustration purposes):
Copy code
fun <E> List<E>.myDistinct(): List<E> {
    val u = mutableListOf<E>()
    this.asSequence().filter { it !in u }.forEach { u += it }
    return u
}

println(listOf(1, 2, 5, 2, 1, 1, 3, 4).myDistinct()) // [1, 2, 5, 3, 4]
Suppose you thought, "Oh, this list is so small, I might as well do the operations directly on the list instead of turning it into a sequence":
Copy code
fun <E> List<E>.myDistinct(): List<E> {
    val u = mutableListOf<E>()
    this.filter { it !in u }.forEach { u += it }
    return u
}

println(listOf(1, 2, 5, 2, 1, 1, 3, 4).myDistinct()) // nothing is filtered out!
The second snippet does not work because the mutable list
u
is always empty while the
filter
operation is performed on every element.
s
👍 nice example!
d
Yeah, I see that. Though that’s what happens when you have an operation with side-effects.
1
w
When specifically talking about Advent of Code, I'd recommend starting by using sequences for their semantics, not for performance, unless you get actual performance issues. Especially in the scope of Advent of Code, where the issue is only algorithmic complexity, I yet have to do an Advent of Code day where I needed sequences for their performance. Lists are extremely debugger friendly, being able to quickly evaluate them is very insightful (or print them, if you are a println debugger :)) However, sequences are amazing, knowing where to use them is amazing, and additionally, it will get mentally prepare you for `Flow`'s, which have some similar semantics. Here is 2 simple examples of this year's calendar where I show the usage of both lists as well as sequences: day 9, notice how I don't even care to optimize `.map(...).sum()`:
Copy code
fun day9a(input: String): Int {
    return input
        .lines()
        .map { it.split(" ").map { it.toInt() } }
        .map { history ->
            generateSequence(history) { it.zipWithNext { l, r -> r - l } }
                .takeWhile { !it.all { it == 0 } }
                .sumOf { it.last() }
        }.sum()
}
Day 8:
Copy code
fun String.infinitelyRepeating(): Sequence<Char> = generateSequence { asSequence() }.flatten()

fun Node.numberOfStepsUntilReachingFirstEndNode(): Int =
    directionSequence
        .infinitelyRepeating()
        .runningFold(this) { current, direction -> current.findNextNodeGoingTowards(direction) }
        .indexOfFirst { it.isEndNode() }
If you ever think you needed sequences for their performance in AoC, please let me know 🙂