Последний элемент последовательности, но с разрывом, если условие выполнено

Допустим, у меня есть 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 вернет ноль, вся последовательность будет использована.

Я могу сделать это итеративно, но предпочел бы функциональное решение.

Кстати, в гибридном языке, таком как Kotlin, многие функции stdlib, которые позволяют писать код функционального стиля, на самом деле реализованы с помощью кода императивного стиля, по крайней мере частично; это кажется хорошим компромиссом, поскольку чисто функциональный код может быть сложным и/или работать плохо.  Так что нет ничего постыдного в том, чтобы использовать этот подход самостоятельно (как показывают первые два ответа).

gidds 30.05.2024 00:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
83
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете использовать 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() по-прежнему будет использовать всю последовательность даже после обнаружения недопустимого элемента
  • вам нужен разумный элемент «по умолчанию» для инициализации вашего значения

Детская площадка

Спасибо - отметка как принятая, поскольку это чисто функциональный подход, но я считаю ответ Левиафана более читабельным, несмотря на то, что он итеративный.

k314159 29.05.2024 16:48

Я не думаю, что это можно легко сделать с помощью встроенных функций, потому что последний — это особый предикат.

Адаптация существующего 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 являются терминальными операциями.

k314159 29.05.2024 18:33

Другие вопросы по теме

Генерация случайных парольных фраз из наборов строк с помощью secrets.random не является очень случайной
Может ли Seq.truncate после Seq.distinct в F# быть полезным с точки зрения производительности?
Как сделать последовательный анализ более эффективным в R
Создание порядковых номеров с ежечасным сбросом
Покрытие последовательности с использованием R
Можно ли построить последовательность для модели последовательности rnn с текстовым вводом и выводом, который представляет собой числовой шаблон?
Excel автоматически создает число на основе критериев
Отображение номера из последовательности с использованием идентификатора последовательности
Генерация псевдослучайной двоичной последовательности, в которой одно и то же число не встречается более двух раз подряд
Разверните строки по диапазону дат, используя дату начала и окончания, чтобы иметь переменную, наблюдаемую для каждого квартала