xref: /aosp_15_r20/external/kotlinx.coroutines/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)

<lambda>null1 @file:JvmMultifileClass
2 @file:JvmName("FlowKt")
3 @file:Suppress("UNCHECKED_CAST")
4 
5 package kotlinx.coroutines.flow
6 
7 import kotlinx.coroutines.flow.internal.*
8 import kotlinx.coroutines.internal.Symbol
9 import kotlin.jvm.*
10 
11 /**
12  * Accumulates value starting with the first element and applying [operation] to current accumulator value and each element.
13  * Throws [NoSuchElementException] if flow was empty.
14  */
15 public suspend fun <S, T : S> Flow<T>.reduce(operation: suspend (accumulator: S, value: T) -> S): S {
16     var accumulator: Any? = NULL
17 
18     collect { value ->
19         accumulator = if (accumulator !== NULL) {
20             @Suppress("UNCHECKED_CAST")
21             operation(accumulator as S, value)
22         } else {
23             value
24         }
25     }
26 
27     if (accumulator === NULL) throw NoSuchElementException("Empty flow can't be reduced")
28     @Suppress("UNCHECKED_CAST")
29     return accumulator as S
30 }
31 
32 /**
33  * Accumulates value starting with [initial] value and applying [operation] current accumulator value and each element
34  */
foldnull35 public suspend inline fun <T, R> Flow<T>.fold(
36     initial: R,
37     crossinline operation: suspend (acc: R, value: T) -> R
38 ): R {
39     var accumulator = initial
40     collect { value ->
41         accumulator = operation(accumulator, value)
42     }
43     return accumulator
44 }
45 
46 /**
47  * The terminal operator that awaits for one and only one value to be emitted.
48  * Throws [NoSuchElementException] for empty flow and [IllegalArgumentException] for flow
49  * that contains more than one element.
50  */
singlenull51 public suspend fun <T> Flow<T>.single(): T {
52     var result: Any? = NULL
53     collect { value ->
54         require(result === NULL) { "Flow has more than one element" }
55         result = value
56     }
57 
58     if (result === NULL) throw NoSuchElementException("Flow is empty")
59     return result as T
60 }
61 
62 /**
63  * The terminal operator that awaits for one and only one value to be emitted.
64  * Returns the single value or `null`, if the flow was empty or emitted more than one value.
65  */
singleOrNullnull66 public suspend fun <T> Flow<T>.singleOrNull(): T? {
67     var result: Any? = NULL
68     collectWhile {
69         // No values yet, update result
70         if (result === NULL) {
71             result = it
72             true
73         } else {
74             // Second value, reset result and bail out
75             result = NULL
76             false
77         }
78     }
79     return if (result === NULL) null else result as T
80 }
81 
82 /**
83  * The terminal operator that returns the first element emitted by the flow and then cancels flow's collection.
84  * Throws [NoSuchElementException] if the flow was empty.
85  */
firstnull86 public suspend fun <T> Flow<T>.first(): T {
87     var result: Any? = NULL
88     collectWhile {
89         result = it
90         false
91     }
92     if (result === NULL) throw NoSuchElementException("Expected at least one element")
93     return result as T
94 }
95 
96 /**
97  * The terminal operator that returns the first element emitted by the flow matching the given [predicate] and then cancels flow's collection.
98  * Throws [NoSuchElementException] if the flow has not contained elements matching the [predicate].
99  */
firstnull100 public suspend fun <T> Flow<T>.first(predicate: suspend (T) -> Boolean): T {
101     var result: Any? = NULL
102     collectWhile {
103         if (predicate(it)) {
104             result = it
105             false
106         } else {
107             true
108         }
109     }
110     if (result === NULL) throw NoSuchElementException("Expected at least one element matching the predicate $predicate")
111     return result as T
112 }
113 
114 /**
115  * The terminal operator that returns the first element emitted by the flow and then cancels flow's collection.
116  * Returns `null` if the flow was empty.
117  */
firstOrNullnull118 public suspend fun <T> Flow<T>.firstOrNull(): T? {
119     var result: T? = null
120     collectWhile {
121         result = it
122         false
123     }
124     return result
125 }
126 
127 /**
128  * The terminal operator that returns the first element emitted by the flow matching the given [predicate] and then cancels flow's collection.
129  * Returns `null` if the flow did not contain an element matching the [predicate].
130  */
firstOrNullnull131 public suspend fun <T> Flow<T>.firstOrNull(predicate: suspend (T) -> Boolean): T? {
132     var result: T? = null
133     collectWhile {
134         if (predicate(it)) {
135             result = it
136             false
137         } else {
138             true
139         }
140     }
141     return result
142 }
143 
144 /**
145  * The terminal operator that returns the last element emitted by the flow.
146  *
147  * Throws [NoSuchElementException] if the flow was empty.
148  */
lastnull149 public suspend fun <T> Flow<T>.last(): T {
150     var result: Any? = NULL
151     collect {
152         result = it
153     }
154     if (result === NULL) throw NoSuchElementException("Expected at least one element")
155     return result as T
156 }
157 
158 /**
159  * The terminal operator that returns the last element emitted by the flow or `null` if the flow was empty.
160  */
lastOrNullnull161 public suspend fun <T> Flow<T>.lastOrNull(): T? {
162     var result: T? = null
163     collect {
164         result = it
165     }
166     return result
167 }
168