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

Advertisements

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:

  • the fold() will still consume the complete sequence even after encountering an invalid element
  • you need a sensible "default" element to initialize your value

Playground

Leave a ReplyCancel reply