Skip to content

Commit 6ed2f0c

Browse files
Merge pull request #1547 from Kotlin/first-firstOrNull-documentation-tests
Documentation and tests for the `first` and `firstOrNull` functions
2 parents 6765112 + 991760d commit 6ed2f0c

File tree

2 files changed

+623
-0
lines changed
  • core/src
    • main/kotlin/org/jetbrains/kotlinx/dataframe/api
    • test/kotlin/org/jetbrains/kotlinx/dataframe/api

2 files changed

+623
-0
lines changed

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import org.jetbrains.kotlinx.dataframe.columns.SingleColumn
1515
import org.jetbrains.kotlinx.dataframe.columns.asColumnSet
1616
import org.jetbrains.kotlinx.dataframe.columns.size
1717
import org.jetbrains.kotlinx.dataframe.columns.values
18+
import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls
1819
import org.jetbrains.kotlinx.dataframe.documentation.DslGrammarTemplateColumnsSelectionDsl.DslGrammarTemplate
1920
import org.jetbrains.kotlinx.dataframe.documentation.Indent
2021
import org.jetbrains.kotlinx.dataframe.documentation.LineBreak
22+
import org.jetbrains.kotlinx.dataframe.documentation.RowFilterDescription
23+
import org.jetbrains.kotlinx.dataframe.documentation.SelectingColumns
2124
import org.jetbrains.kotlinx.dataframe.impl.columns.TransformableColumnSet
2225
import org.jetbrains.kotlinx.dataframe.impl.columns.singleOrNullWithTransformerImpl
2326
import org.jetbrains.kotlinx.dataframe.impl.columns.transform
@@ -27,32 +30,173 @@ import kotlin.reflect.KProperty
2730

2831
// region DataColumn
2932

33+
/**
34+
* Returns the first value in this [DataColumn].
35+
*
36+
* See also [firstOrNull], [last], [take], [takeLast].
37+
*
38+
* @return The first value in this [DataColumn].
39+
*
40+
* @throws [IndexOutOfBoundsException] if the [DataColumn] is empty.
41+
*/
3042
public fun <T> DataColumn<T>.first(): T = get(0)
3143

44+
/**
45+
* Returns the first value in this [DataColumn]. If the [DataColumn] is empty, returns `null`.
46+
*
47+
* See also [first], [last], [take], [takeLast].
48+
*
49+
* @return The first value in this [DataColumn], or `null` if the [DataColumn] is empty.
50+
*/
3251
public fun <T> DataColumn<T>.firstOrNull(): T? = if (size > 0) first() else null
3352

53+
/**
54+
* Returns the first value in this [DataColumn] that matches the given [predicate].
55+
*
56+
* ### Example
57+
* ```kotlin
58+
* // In a DataFrame of financial transactions sorted by time,
59+
* // find the amount of the first transaction over 100 euros
60+
* df.amount.first { it > 100 }
61+
* ```
62+
*
63+
* See also [firstOrNull], [last], [take], [takeLast].
64+
*
65+
* @param [predicate] A lambda expression used to get the first value
66+
* that satisfies a condition specified in this expression.
67+
* This predicate takes a value from the [DataColumn] as an input
68+
* and returns `true` if the value satisfies the condition or `false` otherwise.
69+
*
70+
* @return The first value in this [DataColumn] that matches the given [predicate].
71+
*
72+
* @throws [NoSuchElementException] if the [DataColumn] contains no elements matching the [predicate]
73+
* (including the case when the [DataColumn] is empty).
74+
*/
3475
public fun <T> DataColumn<T>.first(predicate: (T) -> Boolean): T = values.first(predicate)
3576

77+
/**
78+
* Returns the first value in this [DataColumn] that matches the given [predicate].
79+
* Returns `null` if the [DataColumn] contains no elements matching the [predicate]
80+
* (including the case when the [DataColumn] is empty).
81+
*
82+
* ### Example
83+
* ```kotlin
84+
* // In a DataFrame of financial transactions sorted by time,
85+
* // find the amount of the first transaction over 100 euros,
86+
* // or 'null' if there is no such transaction
87+
* df.amount.firstOrNull { it > 100 }
88+
* ```
89+
*
90+
* See also [first], [last], [take], [takeLast].
91+
*
92+
* @param [predicate] A lambda expression used to get the first value
93+
* that satisfies a condition specified in this expression.
94+
* This predicate takes a value from the [DataColumn] as an input
95+
* and returns `true` if the value satisfies the condition or `false` otherwise.
96+
*
97+
* @return The first value in this [DataColumn] that matches the given [predicate],
98+
* or `null` if the [DataColumn] contains no elements matching the [predicate].
99+
*/
36100
public fun <T> DataColumn<T>.firstOrNull(predicate: (T) -> Boolean): T? = values.firstOrNull(predicate)
37101

38102
// endregion
39103

40104
// region DataFrame
41105

106+
/**
107+
* Returns the first [row][DataRow] in this [DataFrame].
108+
*
109+
* See also [firstOrNull][DataFrame.firstOrNull],
110+
* [last][DataFrame.last],
111+
* [take][DataFrame.take],
112+
* [takeWhile][DataFrame.takeWhile],
113+
* [takeLast][DataFrame.takeLast].
114+
*
115+
* @return A [DataRow] containing the first row in this [DataFrame].
116+
*
117+
* @throws NoSuchElementException if the [DataFrame] contains no rows.
118+
*/
42119
public fun <T> DataFrame<T>.first(): DataRow<T> {
43120
if (nrow == 0) {
44121
throw NoSuchElementException("DataFrame has no rows. Use `firstOrNull`.")
45122
}
46123
return get(0)
47124
}
48125

126+
/**
127+
* Returns the first [row][DataRow] in this [DataFrame]. If the [DataFrame] does not contain any rows, returns `null`.
128+
*
129+
* See also [first][DataFrame.first],
130+
* [last][DataFrame.last],
131+
* [take][DataFrame.take],
132+
* [takeWhile][DataFrame.takeWhile],
133+
* [takeLast][DataFrame.takeLast].
134+
*
135+
* @return A [DataRow] containing the first row in this [DataFrame], or `null` if the [DataFrame] is empty.
136+
*/
49137
public fun <T> DataFrame<T>.firstOrNull(): DataRow<T>? = if (nrow > 0) first() else null
50138

139+
/**
140+
* Returns the first [row][DataRow] in this [DataFrame] that satisfies the given [predicate].
141+
*
142+
* @include [RowFilterDescription]
143+
*
144+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
145+
*
146+
* ### Example
147+
* ```kotlin
148+
* // In a DataFrame of financial transactions sorted by time,
149+
* // find the first transaction with amount over 100 euros
150+
* df.first { amount > 100 }
151+
* ```
152+
*
153+
* See also [firstOrNull][DataFrame.firstOrNull],
154+
* [last][DataFrame.last],
155+
* [take][DataFrame.take],
156+
* [takeWhile][DataFrame.takeWhile],
157+
* [takeLast][DataFrame.takeLast].
158+
*
159+
* @param [predicate] A [row filter][RowFilter] used to get the first value
160+
* that satisfies a condition specified in this filter.
161+
*
162+
* @return A [DataRow] containing the first row that matches the given [predicate].
163+
*
164+
* @throws [NoSuchElementException] if the [DataFrame] contains no rows matching the [predicate].
165+
*/
51166
public inline fun <T> DataFrame<T>.first(predicate: RowFilter<T>): DataRow<T> =
52167
rows().first {
53168
predicate(it, it)
54169
}
55170

171+
/**
172+
* Returns the first [row][DataRow] in this [DataFrame] that satisfies the given [predicate].
173+
* Returns `null` if the [DataFrame] contains no rows matching the [predicate]
174+
* (including the case when the [DataFrame] is empty).
175+
*
176+
* @include [RowFilterDescription]
177+
*
178+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
179+
*
180+
* ### Example
181+
* ```kotlin
182+
* // In a DataFrame of financial transactions sorted by time,
183+
* // find the first transaction with amount over 100 euros,
184+
* // or 'null' if there is no such transaction
185+
* df.firstOrNull { amount > 100 }
186+
* ```
187+
*
188+
* See also [first][DataFrame.first],
189+
* [last][DataFrame.last],
190+
* [take][DataFrame.take],
191+
* [takeWhile][DataFrame.takeWhile],
192+
* [takeLast][DataFrame.takeLast].
193+
*
194+
* @param [predicate] A [row filter][RowFilter] used to get the first value
195+
* that satisfies a condition specified in this filter.
196+
*
197+
* @return A [DataRow] containing the first row that matches the given [predicate],
198+
* or `null` if the [DataFrame] contains no rows matching the [predicate].
199+
*/
56200
public inline fun <T> DataFrame<T>.firstOrNull(predicate: RowFilter<T>): DataRow<T>? =
57201
rows().firstOrNull {
58202
predicate(it, it)
@@ -62,26 +206,186 @@ public inline fun <T> DataFrame<T>.firstOrNull(predicate: RowFilter<T>): DataRow
62206

63207
// region GroupBy
64208

209+
/**
210+
* [Reduces][GroupByDocs.Reducing] the groups of this [GroupBy]
211+
* by taking the first [row][DataRow] from each group,
212+
* and returns a [ReducedGroupBy] containing these rows
213+
* (one [row][DataRow] per group, each [row][DataRow] is the first [row][DataRow] in its group).
214+
*
215+
* If a group in this [GroupBy] is empty,
216+
* the corresponding [row][DataRow] in the resulting [ReducedGroupBy] will contain `null` values
217+
* for all columns in the group, except the grouping key.
218+
*
219+
* ### Example
220+
* ```kotlin
221+
* // In a DataFrame of orders sorted by date and time,
222+
* // find the first order placed by each customer
223+
* df.groupBy { customerId }.first().concat()
224+
* ```
225+
*
226+
* See also [last][GroupBy.last].
227+
*
228+
* @return A [ReducedGroupBy] containing the first [row][DataRow]
229+
* (or a [row][DataRow] with `null` values, except the grouping key) from each group.
230+
*/
65231
@Interpretable("GroupByReducePredicate")
66232
public fun <T, G> GroupBy<T, G>.first(): ReducedGroupBy<T, G> = reduce { firstOrNull() }
67233

234+
/**
235+
* [Reduces][GroupByDocs.Reducing] the groups of this [GroupBy]
236+
* by taking from each group the first [row][DataRow] satisfying the given [predicate],
237+
* and returns a [ReducedGroupBy] containing these rows (one [row][DataRow] per group,
238+
* each [row][DataRow] is the first [row][DataRow] in its group that satisfies the [predicate]).
239+
*
240+
* If the group in [GroupBy] contains no matching rows,
241+
* the corresponding row in [ReducedGroupBy] will contain `null` values for all columns in the group,
242+
* except the grouping key.
243+
*
244+
* @include [RowFilterDescription]
245+
*
246+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
247+
*
248+
* ### Example
249+
* ```kotlin
250+
* // In a DataFrame of orders sorted by date and time,
251+
* // find the first order over 100 euros placed by each customer
252+
* df.groupBy { customerId }.first { total > 100 }.concat()
253+
* ```
254+
*
255+
* See also [last][GroupBy.last].
256+
*
257+
* @param [predicate] A [row filter][RowFilter] used to get the first value
258+
* that satisfies a condition specified in this filter.
259+
*
260+
* @return A [ReducedGroupBy] containing the first [row][DataRow] matching the [predicate]
261+
* (or a [row][DataRow] with `null` values, except the grouping key) from each group.
262+
*/
68263
@Interpretable("GroupByReducePredicate")
69264
public fun <T, G> GroupBy<T, G>.first(predicate: RowFilter<G>): ReducedGroupBy<T, G> = reduce { firstOrNull(predicate) }
70265

71266
// endregion
72267

73268
// region Pivot
74269

270+
/**
271+
* [Reduces][PivotDocs.Reducing] this [Pivot] by taking the first [row][DataRow] from each group,
272+
* and returns a [ReducedPivot] that contains the first [row][DataRow] from the corresponding group in each column.
273+
*
274+
* For more information about [Pivot] with examples: {@include [DocumentationUrls.Pivot]}
275+
*
276+
* ### Example
277+
* ```kotlin
278+
* // In a DataFrame of real estate listings sorted by price,
279+
* // find the cheapest listing for each type of property (house, apartment, etc.)
280+
* df.pivot { type }.first().values()
281+
* ```
282+
*
283+
* See also [pivot], [reduce][Pivot.reduce], [last][Pivot.last].
284+
*
285+
* @return A [ReducedPivot] containing in each column the first [row][DataRow] from the corresponding group.
286+
*/
75287
public fun <T> Pivot<T>.first(): ReducedPivot<T> = reduce { firstOrNull() }
76288

289+
/**
290+
* [Reduces][PivotDocs.Reducing] this [Pivot] by taking from each group the first [row][DataRow]
291+
* satisfying the given [predicate], and returns a [ReducedPivot] that contains the first row, matching the [predicate],
292+
* from the corresponding group in each column.
293+
*
294+
* For more information about [Pivot] with examples: {@include [DocumentationUrls.Pivot]}
295+
*
296+
* @include [RowFilterDescription]
297+
*
298+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
299+
*
300+
* ### Example
301+
* ```kotlin
302+
* // In a DataFrame of real estate listings sorted by price,
303+
* // find the cheapest listing for each type of property (house, apartment, etc.)
304+
* // with is not yet sold out.
305+
* df.pivot { type }.first { !soldOut }.values()
306+
* ```
307+
*
308+
* See also [pivot], [reduce][Pivot.reduce], [last][Pivot.last].
309+
*
310+
* @param [predicate] A [row filter][RowFilter] used to get the first value
311+
* that satisfies a condition specified in this filter.
312+
*
313+
* @return A [ReducedPivot] containing in each column the first [row][DataRow]
314+
* that satisfies the [predicate], from the corresponding group (or a [row][DataRow] with `null` values).
315+
*/
77316
public fun <T> Pivot<T>.first(predicate: RowFilter<T>): ReducedPivot<T> = reduce { firstOrNull(predicate) }
78317

79318
// endregion
80319

81320
// region PivotGroupBy
82321

322+
/**
323+
* [Reduces][PivotGroupByDocs.Reducing] this [PivotGroupBy] by taking the first [row][DataRow]
324+
* from each combined [pivot] + [groupBy] group, and returns a [ReducedPivotGroupBy]
325+
* that contains the first row from each corresponding group.
326+
* If any combined [pivot] + [groupBy] group in [PivotGroupBy] is empty, in the resulting [ReducedPivotGroupBy]
327+
* it will be represented by a [row][DataRow] with `null` values (except the grouping key).
328+
*
329+
* For more information about [PivotGroupBy] with examples: {@include [DocumentationUrls.PivotGroupBy]}
330+
*
331+
* ### Example
332+
* ```kotlin
333+
* // In a DataFrame of real estate listings sorted by price,
334+
* // find the cheapest listing for each combination of type of property (house, apartment, etc.)
335+
* // and the city it is located in
336+
* df.pivot { type }.groupBy { city }.first().values()
337+
* ```
338+
*
339+
* See also [groupBy][Pivot.groupBy],
340+
* [pivot][GroupBy.pivot],
341+
* [reduce][PivotGroupBy.reduce],
342+
* [last][PivotGroupBy.last].
343+
*
344+
* @return A [ReducedPivotGroupBy] containing in each combination of a [groupBy] key and a [pivot] key either
345+
* the first [row][DataRow] of the corresponding [DataFrame] formed by this pivot–group pair,
346+
* or a [row][DataRow] with `null` values (except the grouping key) if this [DataFrame] is empty.
347+
*/
83348
public fun <T> PivotGroupBy<T>.first(): ReducedPivotGroupBy<T> = reduce { firstOrNull() }
84349

350+
/**
351+
* [Reduces][PivotGroupByDocs.Reducing] this [PivotGroupBy]
352+
* by taking from each combined [pivot] + [groupBy] group the first [row][DataRow] satisfying the given [predicate].
353+
* Returns a [ReducedPivotGroupBy] that contains the first row, matching the [predicate], from each corresponding group.
354+
* If any combined [pivot] + [groupBy] group in [PivotGroupBy] does not contain any rows matching the [predicate],
355+
* in the resulting [ReducedPivotGroupBy] it will be represented by a [row][DataRow] with `null` values
356+
* (except the grouping key).
357+
*
358+
* @include [DocumentationUrls.PivotGroupBy]
359+
*
360+
* @include [DocumentationUrls.Pivot]
361+
*
362+
* @include [DocumentationUrls.GroupBy]
363+
*
364+
* @include [RowFilterDescription]
365+
*
366+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
367+
*
368+
* ### Example
369+
* ```kotlin
370+
* // In a DataFrame of real estate listings sorted by price,
371+
* // for each combination of type of property (house, apartment, etc.)
372+
* // and the city it is located in,
373+
* // find the cheapest listing that is not yet sold out
374+
* df.pivot { type }.groupBy { city }.first { !soldOut }.values()
375+
* ```
376+
*
377+
* See also [groupBy][Pivot.groupBy],
378+
* [pivot][GroupBy.pivot],
379+
* [reduce][PivotGroupBy.reduce],
380+
* [last][PivotGroupBy.last].
381+
*
382+
* @param [predicate] A [row filter][RowFilter] used to get the first value
383+
* that satisfies a condition specified in this filter.
384+
*
385+
* @return A [ReducedPivotGroupBy] containing in each combination of a [groupBy] key and a [pivot] key either
386+
* the first matching the [predicate] [row][DataRow] of the corresponding [DataFrame] formed by this pivot–group pair,
387+
* or a [row][DataRow] with `null` values if this [DataFrame] does not contain any rows matching the [predicate].
388+
*/
85389
public fun <T> PivotGroupBy<T>.first(predicate: RowFilter<T>): ReducedPivotGroupBy<T> =
86390
reduce { firstOrNull(predicate) }
87391

0 commit comments

Comments
 (0)