Взятие элементов последовательности, выполняющих предикат, затем продолжение от Kotlin - PullRequest
0 голосов
/ 04 февраля 2020

В Kotlin последовательности есть функция takeWhile, которая позволит вам брать элементы, пока они придерживаются определенного предиката. То, что я хотел бы сделать, это взять элементы в соответствии с этим предикатом, использовать их каким-либо образом, затем изменить предикат и принять следующий «пакет». До сих пор я не нашел способа сделать это исключительно с помощью того, что предлагают последовательности и итераторы.

Следующий фрагмент кода иллюстрирует проблему. Функция primeGenerator() возвращает Sequence простых (длинных) чисел. Предположим, что я хочу создать списки, в которых каждый список имеет простые числа с одинаковым количеством цифр. При создании каждого списка я бы использовал его для какой-то цели. Если список соответствует тому, что я искал, итерация может закончиться, в противном случае перейдите к следующему списку.

val primeIt = primeGenerator().iterator()
var digits = 1
var next: Long? = null
val currentList = ArrayList<Long>()
while (digits < 4) {
    next?.also { currentList.add(it) }
    next = primeIt.next()
    if (next.toString().length > digits) {
        println("Primes with $digits: $currentList")
        currentList.clear()
        digits++
    }
}

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

Ответы [ 3 ]

1 голос
/ 04 февраля 2020

В стандартной библиотеке нет таких функций для больших (или бесконечных) последовательностей, но вы можете написать такую ​​функцию самостоятельно (хотя для этого требуется дополнительный код):

class BufferedIterator<T>(private val iterator: Iterator<T>) : Iterator<T> {

    var current: T? = null
        private set

    var reachedEnd: Boolean = false
        private set

    override fun hasNext(): Boolean = iterator.hasNext().also { reachedEnd = !it }

    override fun next(): T = iterator.next().also { current = it }
}

fun <T> Iterator<T>.buffered() = BufferedIterator(this)

fun <T> BufferedIterator<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
    val list = ArrayList<T>()
    if (reachedEnd) return list
    current?.let {
        if (predicate(it)) list += it
    }
    while (hasNext()) {
        val next = next()
        if (predicate(next)) list += next
        else break
    }
    return list
}

fun main() {
    val sequence = sequence {
        var next = 0
        while (true) {
            yield(next++)
        }
    }
    val iter = sequence.iterator().buffered()
    for (i in 0..3) {
        println(iter.takeWhile { it.toString().length <= i })
    }
}

При таком подходе вы можете легко работать даже с бесконечными последовательностями.

1 голос
/ 04 февраля 2020

Я считаю, что есть способ выполнить sh то, что вы хотите, используя стандартную библиотеку. Сначала ограничьте последовательность, а затем groupBy количество цифр.

val Int.numberOfDigits 
    get() = this.toString().length
sequenceOf(1,22,333).takeWhile{ it.numberOfDigits < 3 }.groupBy{ it.numberOfDigits }.values

Если вы хотите избежать тщательной оценки groupBy, вы можете вместо этого использовать groupingBy , а затем reduce потенциально оставив аккумулятор пустым.

0 голосов
/ 04 февраля 2020

ответ ardenit кажется лучшим подходом для повторного использования. Поскольку для получения «фрагментов» последовательности требуется некоторое состояние, вряд ли что-то легко сделать чисто функциональным образом. Делегирование состояния отдельному классу, охватывающему последовательность, имеет смысл.

Вот небольшой фрагмент, показывающий, что я в итоге использовал. Это предполагает, что последовательность не будет пустой и (технически) бесконечна, или в какой-то момент дальнейшие результаты не запрашиваются.

class ChunkedIterator<T>(seq: Sequence<T>) {
    private val it = seq.iterator()
    var next: T = it.next()
    fun next(predicate: (T) -> Boolean): List<T> {
        val result = ArrayList<T>();
        while (predicate.invoke(next)) {
            result.add(next)
            next = it.next();
        }
        return result
    }
}
...