Need takeLast for Sequence

Kotlin version : 1.9.21

Issue: Need takeLast(n: Int) for Sequence - to take last n items from the sequence.

You could write one yourself fairly easily. What about this?

fun <T> Sequence<T>.takeLast(n: Int): Sequence<T> {
    val queue = ArrayDeque<T>(n)
    
    this@takeLast.forEach {
        queue.addLast(it)
        
        if (queue.size > n)
            queue.removeFirst()
    }
    
    return queue.asSequence()
}

Yeah it buffers n number of items in memory, but I can’t see a way around that. Since the number of elements a Sequence will generate is unknown, you can’t count the elements and say “after 15 elements, return the remaining elements”.

1 Like
fun <T> Sequence<T>.takeLast(n: Int): List<T> {
    val buf = ArrayDeque<T>(n)
    forEach {
        if (buf.size == n) {
            buf.removeFirst()
        }
        buf.addLast(it)
    }
    return buf
}

Please note this is not as useful as some other operators or as list.takeLast(), because we have to consume the whole sequence anyway to get to last elements. Having said that, it is still useful, because we look for last items in a smart way.

edit:
Haha, Skater901 was 5 seconds faster :smiley:

1 Like

YESSSSSSSSS I beat you to the answer!

1 Like

Although, your solution requires to grow the queue once :stuck_out_tongue:

1 Like

Yeah I wasn’t exactly sure what setting the initial capacity meant… I didn’t know if that was a hard limit or what. I’ve never used an ArrayDeque. :smiley:

1 Like

yeah i can do it my self, but why the language doesn’t have this as built in feature.
the List have this feature but Sequence does not, it there any particular reason why the language don’t have it.

there must be a way to not consuming the whole sequence ?

like this for example.

val sec = (1..100)
            .asSequence()
val size = sec.count()
val last3 = sec
     .filterIndexed { index, i ->
         index >= size - 3
     }

this filter only interact with the index without touching the element.
but my request is , i wish the language have built in feature for this.

Sequence tent to be lazy, so it doesn’t make sense to me to buffer it.
I’d like to keep it lazy.

No. Sequence is just an iterator. The only thing we can do with it is to iterate over it - always from the beginning to the end. In your example you actually iterate over all items in the sequence not once, but twice. First to do count() and second in filterIndexed(). It doesn’t matter we don’t touch the element, the sequence produces it anyway.

The problem is that we don’t know when the sequence ends until it ends. So either we use some kind of a buffer or we consume the whole sequence twice - first to count items, second to provide items lazily.

I don’t know what’s the reason it was not added to stdlib, but again, it may be so people don’t think it is a lightweight, simple operation - as you do. If we do list.takeLast(3) then it simply jumps to the end and takes 3 items. If we do seq.takeLast(3) and the sequence has 500 items, then this is skipping 497 and taking last 3. Even worse, we don’t even know upfront we have to skip exactly 497 items, so we have to keep last 3 consumed items all the time to be prepared for the end of the sequence.

3 Likes