Допустим, у меня есть Sequence<Int> неизвестного происхождения (не обязательно из коллекции) и неизвестного, но конечного размера:
val seq = sequenceOf(1, 2, -3, 4, 5, /* ... */)
Предположим, что последовательность достаточно велика, поэтому нежелательно превращать всю последовательность в List.
Я хочу получить последний элемент последовательности:
val last = seq.last()
Но я также хочу перехватить любой «недопустимый» элемент, который может появиться (скажем, отрицательные числа недопустимы), и вернуть первый такой элемент:
val invalid = seq.first { it < 0 }
Но как я могу сделать и то, и другое одновременно?
val lastUnlessInvalidElementPresent = seq.firstOrNull { it < 0 } ?: seq.last()
Проблема в том, что ?: seq.last() не работает, потому что к тому времени, когда firstOrNull вернет ноль, вся последовательность будет использована.
Я могу сделать это итеративно, но предпочел бы функциональное решение.





Вы можете использовать fold() с простым классом данных, который содержит ваше значение плюс флаг, указывающий, находитесь ли вы все еще в режиме «получить последним» или уже в режиме «обнаружено недопустимое значение, давайте сохраним его»:
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)
}
Однако у этого подхода есть два недостатка:
fold() по-прежнему будет использовать всю последовательность даже после обнаружения недопустимого элементаСпасибо - отметка как принятая, поскольку это чисто функциональный подход, но я считаю ответ Левиафана более читабельным, несмотря на то, что он итеративный.
Я не думаю, что это можно легко сделать с помощью встроенных функций, потому что последний — это особый предикат.
Адаптация существующего lastOrNull для проверки первого появления предиката (it < 0) будет выглядеть так:
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
}
Теперь вы можете использовать seq.firstMatchOrLastOrNull { it < 0 }, чтобы получить желаемое. С точки зрения производительности это не должно отличаться от встроенных функций.
В конце концов я придумал свое собственное краткое решение:
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
}
Он почти функционален, но не является чисто функциональным, поскольку использует изменяемую переменную.
fun <T> Sequence<T>.firstMatchOrLastOrNull(predicate: (T) -> Boolean): T? {
return this.find(predicate) ?: this.lastOrNull()
}
println(sequenceOf(1, 2, -3, 4, 5).firstMatchOrLastOrNull { it < 0 }) // -3
println(sequenceOf(1, 2, 3, 4, 5).firstMatchOrLastOrNull { it < 0 }) // 5
println(sequenceOf<Int>().firstMatchOrLastOrNull { it < 0 }) // null
Спасибо, это работает для некоторых последовательностей, но не для тех, которые можно повторить только один раз, потому что и find, и lastOrNull являются терминальными операциями.
Кстати, в гибридном языке, таком как Kotlin, многие функции stdlib, которые позволяют писать код функционального стиля, на самом деле реализованы с помощью кода императивного стиля, по крайней мере частично; это кажется хорошим компромиссом, поскольку чисто функциональный код может быть сложным и/или работать плохо. Так что нет ничего постыдного в том, чтобы использовать этот подход самостоятельно (как показывают первые два ответа).