kotlin takeWhile,包含与 predicate 匹配的实际值(takeWhileInclusive)

xlpyo6sf  于 2023-04-07  发布在  Kotlin
关注(0)|答案(2)|浏览(124)

我经常遇到这样的问题,我想从一个列表中收集值,直到一个值匹配,但我也需要匹配的值本身。虽然takeWhile在这方面几乎是完美的,但它实际上不允许保留最后一个(或基本上匹配的)条目。
举个简单的例子:显示对象的类层次结构,直到实现特定接口的第一个类

generateSequence(obj::class.java, Class<*>::getSuperclass)
        .takeWhile { interestedType !in it.interfaces }
        .joinToString(" > ")
        .run(::println)

对于obj=arrayListOf(1)interestedType=Collection::class.java,我希望看到如下内容:

class java.util.ArrayList > class java.util.AbstractList > class java.util.AbstractCollection

我希望它会像这样简单:

generateSequence(obj::class.java, Class<*>::getSuperclass)
        .takeWhileInclusive { interestedType !in it.interfaces }
        .joinToString(" > ")
        .run(::println)

但是这样的函数并不存在(还没有?)。但是也许有其他的函数真的很接近这个函数?或者也许最多连续调用两次函数,它就已经很容易实现了,我只是没有看到它?
我不想找的:我如何解决关于层次结构中的哪个类实现接口的特定问题。这只是一个简单的例子。我也没有寻找:如何使用Iterator或基本的while-/for-loop实现它...(除了:如果它容易阅读,并且不超过3行,那么...也许;- )).
我发现:Is this implementation of takeWhileInclusive safe?也链接了它自己的takeWhileInclusive的实现(和它的灵感)。然而,我真的不喜欢它使用var来注册它是否找到了匹配......当我读到评论(“假设顺序”)时,我也有点不确定这个实现是否真的有意义/真的安全。

mqxuamgl

mqxuamgl1#

我还没有找到合适的现有函数,我也不太喜欢链接的解决方案,所以我玩了一下,最终得到了以下扩展函数:

fun <T> Sequence<T>.takeWhileInclusive(predicate: (T) -> Boolean) = sequence {
    with(iterator()) {
        while (hasNext()) {
            val next = next()
            yield(next)
            if (!predicate(next)) break
        }
    }
}

它使用sequence,在需要时缓慢地产生值。至少我可以省略中间的var,我认为这样可能更有益...

ffvjumwh

ffvjumwh2#

遗憾的是,takeWhile使用的几个函数是内部的。但是如果我们将它们复制到扩展文件中,我们也可以为流构建takeWhileInclusive

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import java.util.concurrent.CancellationException

fun <T> Flow<T>.takeWhileInclusive(predicate: suspend (T) -> Boolean): Flow<T> = flow {
    // This return is needed to work around a bug in JS BE: KT-39227
    return@flow collectWhile { value ->
        emit(value)
        predicate(value)
    }
}

private suspend inline fun <T> Flow<T>.collectWhile(crossinline predicate: suspend (value: T) -> Boolean) {
    val collector = object : FlowCollector<T> {
        override suspend fun emit(value: T) {
            // Note: we are checking predicate first, then throw. If the predicate does suspend (calls emit, for example)
            // the the resulting code is never tail-suspending and produces a state-machine
            if (!predicate(value)) {
                throw AbortFlowException(this)
            }
        }
    }
    try {
        collect(collector)
    } catch (e: AbortFlowException) {
        e.checkOwnership(collector)
    }
}

private class AbortFlowException constructor(
    @JvmField @Transient val owner: FlowCollector<*>
) : CancellationException("Flow was aborted, no more elements needed") {

    override fun fillInStackTrace(): Throwable {
        // Prevent Android <= 6.0 bug, #1866
        stackTrace = emptyArray()
        return this
    }
}

private fun AbortFlowException.checkOwnership(owner: FlowCollector<*>) {
    if (this.owner !== owner) throw this
}

我不喜欢Kotlin内部代码的复制粘贴,但还没有找到更好的方法来实现它。

相关问题