Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 784
k314159 Avatar asked Sep 07 '25 19:09

k314159


2 Answers

I don't think this can be easily done with the built-in functions because last is a somewhat special predicate.

Adapting the existing lastOrNull to test for the first occurrence of a predicate (it < 0) would look like this:

inline fun <T> Sequence<T>.firstMatchOrLastOrNull(predicate: (T) -> Boolean): T? {
    var last: T? = null
    for (element in this) {
        if (predicate(element)) return element
        last = element
    }
    return last
}

Now you can use seq.firstMatchOrLastOrNull { it < 0 } to get what you want. This shouldn't be different, performance-wise, from the built-in functions.

like image 109
Leviathan Avatar answered Sep 10 '25 21:09

Leviathan


In the end I've come up with my own succinct solution:

fun <T> Sequence<T>.firstMatchOrLastOrNull(predicate: (T) -> Boolean): T? {
    var last: T? = null
    return onEach { last = it }.firstOrNull(predicate) ?: last
}

fun main() {
    println(sequenceOf(1, 2, -3, 4, 5).firstMatchOrLastOrNull { it < 0 }) // -3
    println(sequenceOf(1, 2, 3, 4, 5).firstMatchOrLastOrNull { it < 0 })  // 5
}

It's almost functional, but it's not pure functional because it uses a mutable variable.

like image 32
k314159 Avatar answered Sep 10 '25 21:09

k314159