kotlinsequence

Last element of sequence but with a break if a condition is met


Let say I have a Sequence<Int> of unknown origin (not necessarily from a collection) and unknown but finite size:

val seq = sequenceOf(1, 2, -3, 4, 5, /* ... */)

Assume the sequence is large enough to make it undesirable to turn the whole sequence into a List.

I want to get the last element of the sequence:

val last = seq.last()

But I also want to catch any "invalid" element that might appear (let's say negative numbers are invalid) and return the first such element:

val invalid = seq.first { it < 0 }

But how can I do both of those at the same time?

val lastUnlessInvalidElementPresent = seq.firstOrNull { it < 0 } ?: seq.last()

The problem is that ?: seq.last() doesn't work because by the time firstOrNull has returned null, the whole sequence has been consumed.

I can do this iteratively, but would prefer a functional solution.


Solution

  • You can use fold() with a simple data class that holds your value plus a flag indicating whether you're still in "get last" mode or already in "encountered an invalid value, let's keep it" mode:

    data class Elem(val value: Int, val valid: Boolean)
    
    fun main() {
        val seq = sequenceOf(1, 2, -3, 4, 5, /* ... */)
        val initial = Elem(0, true)
        val lastUnlessInvalidElementPresent = seq.fold(Elem(0, true), 
           { acc, item -> if (acc.valid && item < 0) { Elem(item, false) }   // first invalid element
                          else if (acc.valid) { Elem(item, true) } // valid element, keep looking
                          else { acc } // already found an invalid element - keep it
           }
        )
        println(lastUnlessInvalidElementPresent)
    }
    

    This approach has two disadvantages, though:

    Playground