-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
Grouping.kt
282 lines (259 loc) · 13.2 KB
/
Grouping.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:kotlin.jvm.JvmName("GroupingKt")
@file:kotlin.jvm.JvmMultifileClass
package kotlin.collections
/**
* Represents a source of elements with a [keyOf] function, which can be applied to each element to get its key.
*
* A [Grouping] structure serves as an intermediate step in group-and-fold operations:
* they group elements by their keys and then fold each group with some aggregating operation.
*
* It is created by attaching `keySelector: (T) -> K` function to a source of elements.
* To get an instance of [Grouping] use one of `groupingBy` extension functions:
* - [Iterable.groupingBy]
* - [Sequence.groupingBy]
* - [Array.groupingBy]
* - [CharSequence.groupingBy]
*
* For the list of group-and-fold operations available, see the [extension functions](#extension-functions) for `Grouping`.
*/
@SinceKotlin("1.1")
public interface Grouping<T, out K> {
/** Returns an [Iterator] over the elements of the source of this grouping. */
fun sourceIterator(): Iterator<T>
/** Extracts the key of an [element]. */
fun keyOf(element: T): K
}
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments, and stores the results in a new map.
*
* The key for each element is provided by the [Grouping.keyOf] function.
*
* @param operation function is invoked on each element with the following parameters:
* - `key`: the key of the group this element belongs to;
* - `accumulator`: the current value of the accumulator of the group, can be `null` if it's the first `element` encountered in the group;
* - `element`: the element from the source being aggregated;
* - `first`: indicates whether it's the first `element` encountered in the group.
*
* @return a [Map] associating the key of each group with the result of aggregation of the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.aggregate(
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): Map<K, R> {
return aggregateTo(mutableMapOf<K, R>(), operation)
}
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments,
* and stores the results in the given [destination] map.
*
* The key for each element is provided by the [Grouping.keyOf] function.
*
* @param operation a function that is invoked on each element with the following parameters:
* - `key`: the key of the group this element belongs to;
* - `accumulator`: the current value of the accumulator of the group, can be `null` if it's the first `element` encountered in the group;
* - `element`: the element from the source being aggregated;
* - `first`: indicates whether it's the first `element` encountered in the group.
*
* If the [destination] map already has a value corresponding to some key,
* then the elements being aggregated for that key are never considered as `first`.
*
* @return the [destination] map associating the key of each group with the result of aggregation of the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.aggregateTo(
destination: M,
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): M {
for (e in this.sourceIterator()) {
val key = keyOf(e)
val accumulator = destination[key]
destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key))
}
return destination
}
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments, and stores the results in a new map.
* An initial value of accumulator is provided by [initialValueSelector] function.
*
* @param initialValueSelector a function that provides an initial value of accumulator for each group.
* It's invoked with parameters:
* - `key`: the key of the group;
* - `element`: the first element being encountered in that group.
*
* @param operation a function that is invoked on each element with the following parameters:
* - `key`: the key of the group this element belongs to;
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being accumulated.
*
* @return a [Map] associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.fold(
initialValueSelector: (key: K, element: T) -> R,
operation: (key: K, accumulator: R, element: T) -> R
): Map<K, R> =
@Suppress("UNCHECKED_CAST")
aggregate { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) }
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments,
* and stores the results in the given [destination] map.
* An initial value of accumulator is provided by [initialValueSelector] function.
*
* @param initialValueSelector a function that provides an initial value of accumulator for each group.
* It's invoked with parameters:
* - `key`: the key of the group;
* - `element`: the first element being encountered in that group.
*
* If the [destination] map already has a value corresponding to some key, that value is used as an initial value of
* the accumulator for that group and the [initialValueSelector] function is not called for that group.
*
* @param operation a function that is invoked on each element with the following parameters:
* - `key`: the key of the group this element belongs to;
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being accumulated.
*
* @return the [destination] map associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.foldTo(
destination: M,
initialValueSelector: (key: K, element: T) -> R,
operation: (key: K, accumulator: R, element: T) -> R
): M =
@Suppress("UNCHECKED_CAST")
aggregateTo(destination) { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) }
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments, and stores the results in a new map.
* An initial value of accumulator is the same [initialValue] for each group.
*
* @param operation a function that is invoked on each element with the following parameters:
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being accumulated.
*
* @return a [Map] associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.fold(
initialValue: R,
operation: (accumulator: R, element: T) -> R
): Map<K, R> =
@Suppress("UNCHECKED_CAST")
aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) }
/**
* Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially,
* passing the previously accumulated value and the current element as arguments,
* and stores the results in the given [destination] map.
* An initial value of accumulator is the same [initialValue] for each group.
*
* If the [destination] map already has a value corresponding to the key of some group,
* that value is used as an initial value of the accumulator for that group.
*
* @param operation a function that is invoked on each element with the following parameters:
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being accumulated.
*
* @return the [destination] map associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.foldTo(
destination: M,
initialValue: R,
operation: (accumulator: R, element: T) -> R
): M =
@Suppress("UNCHECKED_CAST")
aggregateTo(destination) { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) }
/**
* Groups elements from the [Grouping] source by key and applies the reducing [operation] to the elements of each group
* sequentially starting from the second element of the group,
* passing the previously accumulated value and the current element as arguments,
* and stores the results in a new map.
* An initial value of accumulator is the first element of the group.
*
* @param operation a function that is invoked on each subsequent element of the group with the following parameters:
* - `key`: the key of the group this element belongs to;
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being accumulated.
*
* @return a [Map] associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <S, T : S, K> Grouping<T, K>.reduce(
operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S> =
aggregate { key, acc, e, first ->
@Suppress("UNCHECKED_CAST")
if (first) e else operation(key, acc as S, e)
}
/**
* Groups elements from the [Grouping] source by key and applies the reducing [operation] to the elements of each group
* sequentially starting from the second element of the group,
* passing the previously accumulated value and the current element as arguments,
* and stores the results in the given [destination] map.
* An initial value of accumulator is the first element of the group.
*
* If the [destination] map already has a value corresponding to the key of some group,
* that value is used as an initial value of the accumulator for that group and the first element of that group is also
* subjected to the [operation].
* @param operation a function that is invoked on each subsequent element of the group with the following parameters:
* - `accumulator`: the current value of the accumulator of the group;
* - `element`: the element from the source being folded;
*
* @return the [destination] map associating the key of each group with the result of accumulating the group elements.
*/
@SinceKotlin("1.1")
public inline fun <S, T : S, K, M : MutableMap<in K, S>> Grouping<T, K>.reduceTo(
destination: M,
operation: (key: K, accumulator: S, element: T) -> S
): M =
aggregateTo(destination) { key, acc, e, first ->
@Suppress("UNCHECKED_CAST")
if (first) e else operation(key, acc as S, e)
}
/**
* Groups elements from the [Grouping] source by key and counts elements in each group to the given [destination] map.
*
* If the [destination] map already has a value corresponding to the key of some group,
* that value is used as an initial value of the counter for that group.
*
* @return the [destination] map associating the key of each group with the count of elements in the group.
*
* @sample samples.collections.Collections.Transformations.groupingByEachCount
*/
@SinceKotlin("1.1")
public fun <T, K, M : MutableMap<in K, Int>> Grouping<T, K>.eachCountTo(destination: M): M =
foldTo(destination, 0) { acc, _ -> acc + 1 }
/*
/**
* Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group
* to the given [destination] map.
*
*
* If the [destination] map already has a value corresponding to the key of some group,
* that value is used as an initial value of the sum for that group.
*
* @return the [destination] map associating the key of each group with the sum of elements in the group.
*/
@SinceKotlin("1.1")
public inline fun <T, K, M : MutableMap<in K, Int>> Grouping<T, K>.eachSumOfTo(destination: M, valueSelector: (T) -> Int): M =
foldTo(destination, 0) { acc, e -> acc + valueSelector(e)}
*/
/*
// TODO: sum by long and by double overloads
public inline fun <T, K, M : MutableMap<in K, Long>> Grouping<T, K>.sumEachByLongTo(destination: M, valueSelector: (T) -> Long): M =
foldTo(destination, 0L) { acc, e -> acc + valueSelector(e)}
public inline fun <T, K> Grouping<T, K>.sumEachByLong(valueSelector: (T) -> Long): Map<K, Long> =
fold(0L) { acc, e -> acc + valueSelector(e)}
public inline fun <T, K, M : MutableMap<in K, Double>> Grouping<T, K>.sumEachByDoubleTo(destination: M, valueSelector: (T) -> Double): M =
foldTo(destination, 0.0) { acc, e -> acc + valueSelector(e)}
public inline fun <T, K> Grouping<T, K>.sumEachByDouble(valueSelector: (T) -> Double): Map<K, Double> =
fold(0.0) { acc, e -> acc + valueSelector(e)}
*/