@@ -18,6 +18,8 @@ import org.jetbrains.kotlinx.dataframe.columns.values
1818import org.jetbrains.kotlinx.dataframe.documentation.DslGrammarTemplateColumnsSelectionDsl.DslGrammarTemplate
1919import org.jetbrains.kotlinx.dataframe.documentation.Indent
2020import org.jetbrains.kotlinx.dataframe.documentation.LineBreak
21+ import org.jetbrains.kotlinx.dataframe.documentation.RowFilterDescription
22+ import org.jetbrains.kotlinx.dataframe.documentation.SelectingColumns
2123import org.jetbrains.kotlinx.dataframe.impl.columns.TransformableColumnSet
2224import org.jetbrains.kotlinx.dataframe.impl.columns.singleOrNullWithTransformerImpl
2325import org.jetbrains.kotlinx.dataframe.impl.columns.transform
@@ -27,32 +29,141 @@ import kotlin.reflect.KProperty
2729
2830// region DataColumn
2931
32+ /* *
33+ * Returns the first value in this [DataColumn].
34+ *
35+ * @param T The type of the values in the [DataColumn].
36+ *
37+ * @throws [IndexOutOfBoundsException] if the [DataColumn] is empty.
38+ */
3039public fun <T > DataColumn<T>.first (): T = get(0 )
3140
41+ /* *
42+ * Returns the first value in this [DataColumn]. If the [DataColumn] is empty, returns `null`.
43+ *
44+ * @param T The type of the values in the [DataColumn].
45+ */
3246public fun <T > DataColumn<T>.firstOrNull (): T ? = if (size > 0 ) first() else null
3347
48+ /* *
49+ * Returns the first value in this [DataColumn] that matches the given [predicate].
50+ *
51+ * ### Example
52+ * ```kotlin
53+ * // Select from the column "age" the first value where the age is greater than 17
54+ * df.age.first { it > 17 }
55+ * ```
56+ *
57+ * @param T The type of the values in the [DataColumn].
58+ * @param predicate A lambda expression used to select a value
59+ * that satisfies a condition specified in this expression.
60+ * This predicate takes a value from the [DataColumn] as an input
61+ * and returns `true` if the value satisfies the condition or `false` otherwise.
62+ *
63+ * @throws [NoSuchElementException] if the [DataColumn] contains no element matching the [predicate]
64+ * (including the case when the [DataColumn] is empty).
65+ */
3466public fun <T > DataColumn<T>.first (predicate : (T ) -> Boolean ): T = values.first(predicate)
3567
68+ /* *
69+ * Returns the first value in this [DataColumn] that matches the given [predicate].
70+ * Returns `null` if the [DataColumn] contains no element matching the [predicate]
71+ * (including the case when the [DataColumn] is empty).
72+ *
73+ * ### Example
74+ * ```kotlin
75+ * // Select from the column "age" the first value where the age is greater than 17,
76+ * // or null if there is no such value
77+ * df.age.firstOrNull { it > 17 }
78+ * ```
79+ *
80+ * @param T The type of the values in the [DataColumn].
81+ * @param predicate A lambda expression used to select a value
82+ * that satisfies a condition specified in this expression.
83+ * This predicate takes a value from the [DataColumn] as an input
84+ * and returns `true` if the value satisfies the condition or `false` otherwise.
85+ */
3686public fun <T > DataColumn<T>.firstOrNull (predicate : (T ) -> Boolean ): T ? = values.firstOrNull(predicate)
3787
3888// endregion
3989
4090// region DataFrame
4191
92+ /* *
93+ * Returns the first row in this [DataFrame].
94+ *
95+ * @param T The type of the [DataFrame].
96+ *
97+ * @throws NoSuchElementException if the [DataFrame] contains no rows.
98+ */
4299public fun <T > DataFrame<T>.first (): DataRow <T > {
43100 if (nrow == 0 ) {
44101 throw NoSuchElementException (" DataFrame has no rows. Use `firstOrNull`." )
45102 }
46103 return get(0 )
47104}
48105
106+ /* *
107+ * Returns the first row in this [DataFrame]. If the [DataFrame] does not contain any rows, returns `null`.
108+ *
109+ * @param T The type of the [DataFrame].
110+ */
49111public fun <T > DataFrame<T>.firstOrNull (): DataRow <T >? = if (nrow > 0 ) first() else null
50112
113+ /* *
114+ * Returns the first row in this [DataFrame] that satisfies the given [predicate].
115+ *
116+ * {@include [RowFilterDescription]}
117+ *
118+ * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
119+ *
120+ * ### Example
121+ * ```kotlin
122+ * // Select the first row where the value in the "age" column is greater than 17
123+ * // and the "name/firstName" column starts with 'A'
124+ * df.first { age > 17 && name.firstName.startsWith("A") }
125+ * ```
126+ *
127+ * @param T The type of the [DataFrame].
128+ * @param predicate A lambda expression used to select a value
129+ * that satisfies a condition specified in this expression.
130+ * This predicate takes a value from the [DataFrame] as an input
131+ * and returns `true` if the value satisfies the condition or `false` otherwise.
132+ *
133+ * @return A [DataRow] containing the first row that matches the given [predicate].
134+ *
135+ * @throws [NoSuchElementException] if the [DataFrame] contains no rows matching the [predicate].
136+ */
51137public inline fun <T > DataFrame<T>.first (predicate : RowFilter <T >): DataRow <T > =
52138 rows().first {
53139 predicate(it, it)
54140 }
55141
142+ /* *
143+ * Returns the first row in this [DataFrame] that satisfies the given [predicate].
144+ * Returns `null` if the [DataFrame] contains no rows matching the [predicate]
145+ * (including the case when the [DataFrame] is empty).
146+ *
147+ * {@include [RowFilterDescription]}
148+ *
149+ * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
150+ *
151+ * ### Example
152+ * ```kotlin
153+ * // Select the first row where the value in the "age" column is greater than 17
154+ * // and the "name/firstName" column starts with 'A'
155+ * df.firstOrNull { age > 17 && name.firstName.startsWith("A") }
156+ * ```
157+ *
158+ * @param T The type of the [DataFrame].
159+ * @param predicate A lambda expression used to select a value
160+ * that satisfies a condition specified in this expression.
161+ * This predicate takes a value from the [DataFrame] as an input
162+ * and returns `true` if the value satisfies the condition or `false` otherwise.
163+ *
164+ * @return A [DataRow] containing the first row that matches the given [predicate],
165+ * or `null` if the [DataFrame] contains no rows matching the [predicate]
166+ */
56167public inline fun <T > DataFrame<T>.firstOrNull (predicate : RowFilter <T >): DataRow <T >? =
57168 rows().firstOrNull {
58169 predicate(it, it)
@@ -62,26 +173,209 @@ public inline fun <T> DataFrame<T>.firstOrNull(predicate: RowFilter<T>): DataRow
62173
63174// region GroupBy
64175
176+ /* *
177+ * Selects the first row from each group of the given [GroupBy]
178+ * and returns a [ReducedGroupBy] containing these rows
179+ * (one row per group, each row is the first row in its group).
180+ *
181+ * ### Example
182+ * ```kotlin
183+ * // Select the first employee from each group formed by the job title
184+ * employees.groupBy { jobTitle }.first()
185+ * ```
186+ *
187+ * @param T The type of the values in the [GroupBy].
188+ * @param G The type of the groups in the [GroupBy].
189+ *
190+ * @return A [ReducedGroupBy] containing the first row from each group.
191+ */
65192@Interpretable(" GroupByReducePredicate" )
66193public fun <T , G > GroupBy <T , G >.first (): ReducedGroupBy <T , G > = reduce { firstOrNull() }
67194
195+ /* *
196+ * Selects from each group of the given [GroupBy] the first row satisfying the given [predicate],
197+ * and returns a [ReducedGroupBy] containing these rows (one row per group,
198+ * each row is the first row in its group that satisfies the [predicate]).
199+ *
200+ * If the group in [GroupBy] contains no matching rows,
201+ * the corresponding row in [ReducedGroupBy] will contain `null` values for all columns in the group.
202+ *
203+ * {@include [RowFilterDescription]}
204+ *
205+ * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
206+ *
207+ * ### Example
208+ * ```kotlin
209+ * // Select the first employee older than 25 from each group formed by the job title
210+ * employees.groupBy { jobTitle }.first { age > 25 }
211+ * ```
212+ *
213+ * @param T The type of the values in the [GroupBy].
214+ * @param G The type of the groups in the [GroupBy].
215+ * @param predicate A lambda expression used to select a value
216+ * that satisfies a condition specified in this expression.
217+ * This predicate takes a value from the [GroupBy] as an input
218+ * and returns `true` if the value satisfies the condition or `false` otherwise.
219+ *
220+ * @return A [ReducedGroupBy] containing the first row matching the [predicate]
221+ * (or a row with `null` values, except values in the column with the grouping key), from each group.
222+ */
68223@Interpretable(" GroupByReducePredicate" )
69224public fun <T , G > GroupBy <T , G >.first (predicate : RowFilter <G >): ReducedGroupBy <T , G > = reduce { firstOrNull(predicate) }
70225
71226// endregion
72227
73228// region Pivot
74229
230+ /* *
231+ * Reduces this [Pivot] by selecting the first row from each group.
232+ *
233+ * Returns a [ReducedPivot] where:
234+ * - each column corresponds to a [pivot] group — if multiple pivot keys were used,
235+ * the result will contain column groups for each pivot key, with columns inside
236+ * corresponding to the values of that key;
237+ * - each value contains the first row from that group.
238+ *
239+ * The original [Pivot] column structure is preserved.
240+ * If the [Pivot] was created using multiple or nested keys
241+ * (e.g., via [and][PivotDsl.and] or [then][PivotDsl.then]),
242+ * the structure remains unchanged — only the contents of each group
243+ * are replaced with the first row from that group.
244+ *
245+ * Equivalent to `reduce { firstOrNull() }`.
246+ *
247+ * See also:
248+ * - [pivot];
249+ * - common [reduce][Pivot.reduce].
250+ *
251+ * ### Example
252+ * ```kotlin
253+ * // Select the first row for each city.
254+ * // Returns a ReducedPivot with one column per city and the first row from the group in each column.
255+ * df.pivot { city }.first()
256+ * ```
257+ *
258+ * @return A [ReducedPivot] containing in each column the first row from the corresponding group.
259+ */
75260public fun <T > Pivot<T>.first (): ReducedPivot <T > = reduce { firstOrNull() }
76261
262+ /* *
263+ * Reduces this [Pivot] by selecting from each group the first row satisfying the given [predicate].
264+ *
265+ * Returns a [ReducedPivot] where:
266+ * - each column corresponds to a [pivot] group — if multiple pivot keys were used,
267+ * the result will contain column groups for each pivot key, with columns inside
268+ * corresponding to the values of that key;
269+ * - each value contains the first row from that group that satisfies the [predicate],
270+ * or a row with `null` values if no rows in this group match the [predicate].
271+ *
272+ * The original [Pivot] column structure is preserved.
273+ * If the [Pivot] was created using multiple or nested keys
274+ * (e.g., via [and][PivotDsl.and] or [then][PivotDsl.then]),
275+ * the structure remains unchanged — only the contents of each group
276+ * are replaced with the first row from that group that satisfies the [predicate].
277+ *
278+ * Equivalent to `reduce { firstOrNull(predicate) }`.
279+ *
280+ * See also:
281+ * - [pivot];
282+ * - common [reduce][Pivot.reduce].
283+ *
284+ * {@include [RowFilterDescription]}
285+ *
286+ * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
287+ *
288+ * ### Example
289+ * ```kotlin
290+ * // Select the first row for each city where the population is greater than 100 000.
291+ * df.pivot { city }.first { population > 100000 }
292+ * ```
293+ *
294+ * @param predicate A lambda expression used to select a value
295+ * that satisfies a condition specified in this expression.
296+ *
297+ * @return A [ReducedPivot] containing in each column the first row
298+ * that satisfies the [predicate], from the corresponding group (or a row with `null` values)
299+ */
77300public fun <T > Pivot<T>.first (predicate : RowFilter <T >): ReducedPivot <T > = reduce { firstOrNull(predicate) }
78301
79302// endregion
80303
81304// region PivotGroupBy
82305
306+ /* *
307+ * Reduces this [PivotGroupBy] by selecting the first row from each combined [pivot] + [groupBy] group.
308+ *
309+ * Returns a [ReducedPivotGroupBy] containing the following matrix:
310+ * - one row per [groupBy] key (or keys set);
311+ * - one column group per [pivot] key, where each inner column corresponds to a value of that key;
312+ * - each combination of a [groupBy] key and a [pivot] key contains either the first row of the corresponding
313+ * dataframe formed by this pivot–group pair, or a row with `null` values if this dataframe is empty.
314+ *
315+ * The original [PivotGroupBy] column structure is preserved.
316+ * If the [PivotGroupBy] was created using multiple or nested keys
317+ * (e.g., via [and][PivotDsl.and] or [then][PivotDsl.then]),
318+ * the result will contain nested column groups reflecting that key structure,
319+ * with each group containing columns for the values of the corresponding key.
320+ *
321+ * Equivalent to `reduce { firstOrNull() }`.
322+ *
323+ * See also:
324+ * - [pivot], [Pivot.groupBy] and [GroupBy.pivot];
325+ * - common [reduce][PivotGroupBy.reduce].
326+ *
327+ * ### Example
328+ * ```kotlin
329+ * // Select the first student from each combination of faculty and enrollment year.
330+ * students.pivot { faculty }.groupBy { enrollmentYear }.first()
331+ * ```
332+ *
333+ * @return A [ReducedPivotGroupBy] containing in each combination of a [groupBy] key and a [pivot] key either
334+ * the first row of the corresponding dataframe formed by this pivot–group pair,
335+ * or a row with `null` values if this dataframe is empty.
336+ */
83337public fun <T > PivotGroupBy<T>.first (): ReducedPivotGroupBy <T > = reduce { firstOrNull() }
84338
339+ /* *
340+ * Reduces this [PivotGroupBy] by selecting from each combined [pivot] + [groupBy] group
341+ * the first row satisfying the given [predicate].
342+ *
343+ * Returns a [ReducedPivotGroupBy] containing the following matrix:
344+ * - one row per [groupBy] key (or keys set);
345+ * - one column group per [pivot] key, where each inner column corresponds to a value of that key;
346+ * - each combination of a [groupBy] key and a [pivot] key contains either the first matching the [predicate] row
347+ * of the corresponding dataframe formed by this pivot–group pair,
348+ * or a row with `null` values if this dataframe does not contain any rows matching the [predicate].
349+ *
350+ * The original [PivotGroupBy] column structure is preserved.
351+ * If the [PivotGroupBy] was created using multiple or nested keys
352+ * (e.g., via [and][PivotDsl.and] or [then][PivotDsl.then]),
353+ * the result will contain nested column groups reflecting that key structure,
354+ * with each group containing columns for the values of the corresponding key.
355+ *
356+ * Equivalent to `reduce { firstOrNull(predicate) }`.
357+ *
358+ * See also:
359+ * - [pivot], [Pivot.groupBy] and [GroupBy.pivot];
360+ * - common [reduce][PivotGroupBy.reduce].
361+ *
362+ * {@include [RowFilterDescription]}
363+ *
364+ * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention]
365+ *
366+ * ### Example
367+ * ```kotlin
368+ * // From each combination of faculty and enrollment year select the first student older than 21.
369+ * students.pivot { faculty }.groupBy { enrollmentYear }.first { age > 21 }
370+ * ```
371+ *
372+ * @param predicate A lambda expression used to select a value
373+ * that satisfies a condition specified in this expression.
374+ *
375+ * @return A [ReducedPivotGroupBy] containing in each combination of a [groupBy] key and a [pivot] key either
376+ * the first matching the [predicate] row of the corresponding dataframe formed by this pivot–group pair,
377+ * or a row with `null` values if this dataframe does not contain any rows matching the [predicate].
378+ */
85379public fun <T > PivotGroupBy<T>.first (predicate : RowFilter <T >): ReducedPivotGroupBy <T > =
86380 reduce { firstOrNull(predicate) }
87381
0 commit comments