From 44cd48c5bf8f45c8927fdeef86c63904ea5b289e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Mon, 14 Oct 2024 23:22:50 +0200 Subject: [PATCH] Introduce Flow.any, Flow.all, Flow.none See #4212 --- .../common/src/flow/operators/Transform.kt | 43 ++++++++++++++ .../flow/operators/BooleanTerminationTest.kt | 58 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt index f3c9be1c7e..97a0cdcece 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt @@ -164,3 +164,46 @@ public fun Flow.chunked(size: Int): Flow> { result?.let { emit(it) } } } + +/** + * Returns `true` if at least one element matches the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.any + * @see Sequence.any + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = this + .filter { predicate(it) } + .map { true } + .onEmpty { emit(false) } + .first() + +/** + * Returns `true` if all elements match the given [predicate]. + * + * This operation is *terminal*. + * + * Note that if the flow terminates without emitting any elements, the function returns `true` because there + * are no elements in it that *do not* match the predicate. + * See a more detailed explanation of this logic concept in ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * + * @see Iterable.all + * @see Sequence.all + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = + count { !predicate(it) } == 0 + +/** + * Returns `true` if none of the elements match the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.none + * @see Sequence.none + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = + count { predicate(it) } == 0 diff --git a/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt new file mode 100644 index 0000000000..acfb86c3d0 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt @@ -0,0 +1,58 @@ +package kotlinx.coroutines.flow + +import kotlinx.coroutines.testing.* +import kotlin.test.* + +class BooleanTerminationTest : TestBase() { + @Test + fun testAnyNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertTrue(flow.any { it > 0 }) + assertTrue(flow.any { it % 2 == 0 }) + assertFalse(flow.any { it > 5 }) + } + + @Test + fun testAnyEmpty() = runTest { + assertFalse(emptyFlow().any { it > 0 }) + } + + @Test + fun testAllNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertTrue(flow.all { it > 0 }) + assertFalse(flow.all { it % 2 == 0 }) + assertFalse(flow.all { it > 5 }) + } + + @Test + fun testAllEmpty() = runTest { + assertTrue(emptyFlow().all { it > 0 }) + } + + @Test + fun testNoneNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertFalse(flow.none { it > 0 }) + assertFalse(flow.none { it % 2 == 0 }) + assertTrue(flow.none { it > 5 }) + } + + @Test + fun testNoneEmpty() = runTest { + assertTrue(emptyFlow().none { it > 0 }) + } + +}