Skip to content

Commit 6765112

Browse files
Merge pull request #1561 from Kotlin/last-lastOrNull-documentation-tests
Add documentation and tests for the `last` and `lastOrNull` functions
2 parents 4ee79ed + fbe5dc7 commit 6765112

File tree

2 files changed

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

2 files changed

+620
-0
lines changed

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

Lines changed: 303 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,28 +30,167 @@ import kotlin.reflect.KProperty
2730

2831
// region DataColumn
2932

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

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

53+
/**
54+
* Returns the last 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 most recent financial transaction over 100 euros
60+
* df.amount.last { it > 100 }
61+
* ```
62+
*
63+
* See also [lastOrNull], [first], [take], [takeLast].
64+
*
65+
* @param [predicate] A lambda expression used to get the last 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 last value in this [DataColumn] that matches the given [predicate].
71+
*
72+
* @throws [NoSuchElementException] if the [DataColumn] contains no element matching the [predicate]
73+
* (including the case when the [DataColumn] is empty).
74+
*/
3475
public inline fun <T> DataColumn<T>.last(predicate: (T) -> Boolean): T = values.last(predicate)
3576

77+
/**
78+
* Returns the last 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 most recent financial transaction over 100 euros,
86+
* // or 'null' if there is no such transaction
87+
* df.amount.lastOrNull { it > 100 }
88+
* ```
89+
*
90+
* See also [last], [first], [take], [takeLast].
91+
*
92+
* @param [predicate] A lambda expression used to get the last 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 last value in this [DataColumn] that matches the given [predicate],
98+
* or `null` if the [DataColumn] contains no elements matching the [predicate].
99+
*/
36100
public inline fun <T> DataColumn<T>.lastOrNull(predicate: (T) -> Boolean): T? = values.lastOrNull(predicate)
37101

38102
// endregion
39103

40104
// region DataFrame
41105

106+
/**
107+
* Returns the last [row][DataRow] in this [DataFrame] that satisfies the given [predicate].
108+
* Returns `null` if the [DataFrame] contains no rows matching the [predicate]
109+
* (including the case when the [DataFrame] is empty).
110+
*
111+
* @include [RowFilterDescription]
112+
*
113+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
114+
*
115+
* ### Example
116+
* ```kotlin
117+
* // In a DataFrame of financial transactions sorted by time,
118+
* // obtain the most recent financial transaction with amount over 100 euros,
119+
* // or 'null' if there is no such transaction
120+
* df.lastOrNull { amount > 100 }
121+
* ```
122+
*
123+
* See also [last][DataFrame.last],
124+
* [first][DataFrame.first],
125+
* [take][DataFrame.take],
126+
* [takeLast][DataFrame.takeLast],
127+
* [takeWhile][DataFrame.takeWhile].
128+
*
129+
* @param [predicate] A [row filter][RowFilter] used to get the last value
130+
* that satisfies a condition specified in this filter.
131+
*
132+
* @return A [DataRow] containing the last row that matches the given [predicate],
133+
* or `null` if the [DataFrame] contains no rows matching the [predicate].
134+
*/
42135
public inline fun <T> DataFrame<T>.lastOrNull(predicate: RowFilter<T>): DataRow<T>? =
43136
rowsReversed().firstOrNull { predicate(it, it) }
44137

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

170+
/**
171+
* Returns the last [row][DataRow] in this [DataFrame]. If the [DataFrame] does not contain any rows, returns `null`.
172+
*
173+
* See also [last][DataFrame.last],
174+
* [first][DataFrame.first],
175+
* [take][DataFrame.take],
176+
* [takeLast][DataFrame.takeLast].
177+
*
178+
* @return A [DataRow] containing the last row in this [DataFrame], or `null` if the [DataFrame] is empty.
179+
*/
50180
public fun <T> DataFrame<T>.lastOrNull(): DataRow<T>? = if (nrow > 0) get(nrow - 1) else null
51181

182+
/**
183+
* Returns the last [row][DataRow] in this [DataFrame].
184+
*
185+
* See also [lastOrNull][DataFrame.lastOrNull],
186+
* [first][DataFrame.first],
187+
* [take][DataFrame.take],
188+
* [takeLast][DataFrame.takeLast].
189+
*
190+
* @return A [DataRow] containing the last row in this [DataFrame].
191+
*
192+
* @throws NoSuchElementException if the [DataFrame] contains no rows.
193+
*/
52194
public fun <T> DataFrame<T>.last(): DataRow<T> {
53195
if (nrow == 0) {
54196
throw NoSuchElementException("DataFrame has no rows. Use `lastOrNull`.")
@@ -60,26 +202,187 @@ public fun <T> DataFrame<T>.last(): DataRow<T> {
60202

61203
// region GroupBy
62204

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

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

69262
// endregion
70263

71264
// region Pivot
72265

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

285+
/**
286+
* [Reduces][PivotDocs.Reducing] this [Pivot] by taking from each group the last [row][DataRow]
287+
* satisfying the given [predicate], and returns a [ReducedPivot] that contains the last [row][DataRow],
288+
* matching the [predicate], from the corresponding group in each column.
289+
*
290+
* For more information about [Pivot] with examples: {@include [DocumentationUrls.Pivot]}
291+
*
292+
* @include [RowFilterDescription]
293+
*
294+
* @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
295+
*
296+
* ### Example
297+
* ```kotlin
298+
* // In a DataFrame of real estate listings sorted by date and time,
299+
* // find the most recent listing for each type of property (house, apartment, etc.)
300+
* // with the price less than 500,000 euros
301+
* df.pivot { type }.last { price < 500_000 }.values()
302+
* ```
303+
*
304+
* See also [pivot], [reduce][Pivot.reduce], [first][Pivot.first].
305+
*
306+
* @param [predicate] A [row filter][RowFilter] used to get the last value
307+
* that satisfies a condition specified in this filter.
308+
*
309+
* @return A [ReducedPivot] containing in each column the last [row][DataRow]
310+
* that satisfies the [predicate], from the corresponding group (or a [row][DataRow] with `null` values)
311+
*/
75312
public fun <T> Pivot<T>.last(predicate: RowFilter<T>): ReducedPivot<T> = reduce { lastOrNull(predicate) }
76313

77314
// endregion
78315

79316
// region PivotGroupBy
80317

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

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

85388
// endregion

0 commit comments

Comments
 (0)