Как разбить список на подсписки с помощью предиката с Kotlin?

Я пробую идиоматический и идеально функциональный способ разбить список на подсписки в Kotlin.

Представьте, что вводом является ["aaa", "bbb", "", "ccc", "", "ddd", "eee", "fff"], я хочу вернуть [["aaa", "bbb"], ["ccc"], ["ddd", "eee", "fff"]] для данного предиката string.isEmpty().

Это довольно просто сделать с помощью цикла for и аккумулятора; но я не нашел способа написать его функционально, который я считаю достаточно читаемым.

На данный момент мой лучший результат:

lines.foldIndexed(Pair(listOf<List<String>>(), listOf<String>()), { idx, acc, line ->
    when {
    idx + 1 == lines.size -> {
        Pair(acc.first + listOf(acc.second + line), listOf())
    }
    line.isEmpty() -> {
        Pair(acc.first + listOf(acc.second), listOf())
    }
    else -> {
        Pair(acc.first, acc.second + line)
        }
    }
}).first

По сути, я использую fold с двойным аккумулятором, который отслеживает текущий список и сбрасывает его, когда найден предикат. Список подается в полный результат в этой точке. Я использую foldIndexed, чтобы получить свой последний список.

Вы, ребята, знаете какой-нибудь лучший способ?

Для справки, версия цикла может быть

val data = mutableListOf<String>()
var currentData = ""
for(line in lines){
    if (line.isEmpty()) {
        data.add(currentData)
        currentData = ""
    }
    else{
        currentData = "$currentData $line"
    }
}
data.add(currentData)

Спасибо !

У кого-то был точно такой же вопрос на прошлой неделе. Я не думаю, что был найден особенно чистый способ сделать это. Ваш декларативный цикл чище всего, что я видел.

Tenfour04 11.12.2020 14:01

предполагая, что вы бы связали его, если бы он у вас был, но у вас случайно нет ссылки на вопрос? Спасибо!

jlengrand 11.12.2020 15:04

Нашел: stackoverflow.com/q/65140871/506796

Tenfour04 11.12.2020 16:04

Обратите внимание, как CharSequence.split стандартной библиотеки похож на ваш декларативный код: github.com/JetBrains/kotlin/blob/…. Функции функционального стиля — это просто маски над декларативным кодом, поэтому я думаю, что если вы используете что-то неоднократно, вам нужно создать свои собственные функции функционального стиля для этих случаев.

Tenfour04 11.12.2020 16:10

Потрясающе, спасибо! И правда хорошие предложения. Я стараюсь практиковать FP как можно больше, но я также думаю, что это должно иметь смысл. В этом случае я еще не совсем уверен и надеялся на что-то, что я бы пропустил :)

jlengrand 11.12.2020 17:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
2 416
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Я бы предложил сначала найти точки разделения (вручную добавив индексы ребер), а затем сделать срезы:

val lines = listOf("aaa", "bbb", "", "ccc", "", "ddd", "eee", "fff")
val result = lines
    .flatMapIndexed { index, x ->
        when {
            index == 0 || index == lines.lastIndex -> listOf(index)
            x.isEmpty() -> listOf(index - 1, index + 1)
            else -> emptyList()
        }
    }
    .windowed(size = 2, step = 2) { (from, to) -> lines.slice(from..to) }
println(result) //[[aaa, bbb], [ccc], [ddd, eee, fff]]

Спасибо! Идея мне действительно нравится, это выглядит немного лучше, чем таскать с собой пару аккумуляторов.

jlengrand 11.12.2020 17:08

Как бы я включил точки разделения в качестве первого элемента в подсписке? В моем случае я хочу разделить по заголовкам и включить каждый элемент до следующего заголовка.

Sam 26.10.2022 19:41

@Sam, я считаю, что вам нужно настроить эту строку: «x.isEmpty () -> listOf (index - 1, index + 1)». Если вы хотите включить элементы точки разделения в результат в качестве первого элемента, он должен стать " x.isEmpty() -> listOf (индекс - 1, индекс)". Если ваша точка разделения не является пустой строкой, вам также необходимо изменить условие «x.isEmpty()» в зависимости от того, какой «заголовок» в вашем случае.

Михаил Нафталь 27.10.2022 17:56

Вот рекурсивная версия.

fun <T> List<T>.split(predicate: (T) -> Boolean): List<List<T>> {
    val idx = this.indexOfFirst(predicate)
    return if (idx == -1) {
        listOf(this)
    } else {
        return listOf(this.take(idx)) + this.drop(idx + 1).split(predicate)
    }
}

fun main() {
    val lines = listOf("aaa", "bbb", "", "ccc", "", "ddd", "eee", "fff")
    println(lines.split { it.isEmpty() }) // [[aaa, bbb], [ccc], [ddd, eee, fff]]
}

Я не проводил никаких тестов, но предполагаю, что производительность не велика, потому что он дважды выполняет итерацию по списку, один раз для поиска совпадений предикатов и один раз для разделения списка. Можно использовать slice или subList вместо take, а drop дает лучшую производительность.

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