Skip to content

Commit fbcccc4

Browse files
authored
Merge pull request #1474 from CarloMariaProietti/add_move_before
Add move before
2 parents 2be6c10 + 5839b57 commit fbcccc4

File tree

4 files changed

+135
-10
lines changed

4 files changed

+135
-10
lines changed

core/api/core.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,6 +3414,8 @@ public final class org/jetbrains/kotlinx/dataframe/api/MoveKt {
34143414
public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
34153415
public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
34163416
public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lorg/jetbrains/kotlinx/dataframe/columns/ColumnReference;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
3417+
public static final fun before (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
3418+
public static final fun before (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
34173419
public static final fun into (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
34183420
public static final fun into (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
34193421
public static final fun intoIndexed (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function3;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;

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

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -621,13 +621,54 @@ public fun <T, C> MoveClause<T, C>.after(column: KProperty<*>): DataFrame<T> = a
621621

622622
// endregion
623623

624-
/* TODO: implement 'before'
625-
fun <T, C> MoveColsClause<T, C>.before(columnPath: ColumnPath) = before { columnPath.toColumnDef() }
626-
fun <T, C> MoveColsClause<T, C>.before(column: Column) = before { column }
627-
fun <T, C> MoveColsClause<T, C>.before(column: KProperty<*>) = before { column.toColumnDef() }
628-
fun <T, C> MoveColsClause<T, C>.before(column: String) = before { column.toColumnDef() }
629-
fun <T, C> MoveColsClause<T, C>.before(column: ColumnSelector<T, *>) = afterOrBefore(column, false)
630-
*/
624+
// region before
625+
626+
/**
627+
* Moves columns, previously selected with [move] to the position before the
628+
* specified [column] within the [DataFrame].
629+
*
630+
* Returns a new [DataFrame] with updated columns.
631+
*
632+
* See [Selecting Columns][SelectingColumns].
633+
*
634+
* For more information: {@include [DocumentationUrls.Move]}
635+
*
636+
* ### This Before Overload
637+
*/
638+
@ExcludeFromSources
639+
internal interface MoveBefore
640+
641+
/**
642+
* {@include [MoveBefore]}
643+
* @include [SelectingColumns.Dsl]
644+
*
645+
* ### Examples:
646+
* ```kotlin
647+
* df.move { age and weight }.before { surname }
648+
* df.move { cols(3..5) }.before { col(2) }
649+
* ```
650+
*
651+
* @param [column] A [ColumnSelector] specifying the column
652+
* before which the selected columns will be placed.
653+
*/
654+
@Refine
655+
@Interpretable("MoveBefore0")
656+
public fun <T, C> MoveClause<T, C>.before(column: ColumnSelector<T, *>): DataFrame<T> = afterOrBefore(column, false)
657+
658+
/**
659+
* {@include [MoveBefore]}
660+
* @include [SelectingColumns.ColumnNames]
661+
*
662+
* ### Examples:
663+
* ```kotlin
664+
* df.move("age", "weight").before("surname")
665+
* ```
666+
* @param [column] The [Column Name][String] specifying the column
667+
* before which the selected columns will be placed.
668+
*/
669+
public fun <T, C> MoveClause<T, C>.before(column: String): DataFrame<T> = before { column.toColumnAccessor() }
670+
671+
// endregion
631672

632673
@Deprecated(TO_LEFT, ReplaceWith(TO_LEFT_REPLACE), DeprecationLevel.ERROR)
633674
public fun <T, C> MoveClause<T, C>.toLeft(): DataFrame<T> = to(0)

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@ import org.jetbrains.kotlinx.dataframe.DataColumn
66
import org.jetbrains.kotlinx.dataframe.DataFrame
77
import org.jetbrains.kotlinx.dataframe.api.ColumnsSelectionDsl
88
import org.jetbrains.kotlinx.dataframe.api.MoveClause
9+
import org.jetbrains.kotlinx.dataframe.api.after
10+
import org.jetbrains.kotlinx.dataframe.api.asColumnGroup
911
import org.jetbrains.kotlinx.dataframe.api.cast
1012
import org.jetbrains.kotlinx.dataframe.api.getColumn
1113
import org.jetbrains.kotlinx.dataframe.api.getColumnGroup
1214
import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath
15+
import org.jetbrains.kotlinx.dataframe.api.move
1316
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
1417
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
1518
import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
1619
import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy
20+
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
1721
import org.jetbrains.kotlinx.dataframe.impl.DataFrameReceiver
1822
import org.jetbrains.kotlinx.dataframe.impl.asList
1923
import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath
2024
import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition
2125
import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut
26+
import org.jetbrains.kotlinx.dataframe.path
2227

23-
// TODO: support 'before' mode
2428
internal fun <T, C> MoveClause<T, C>.afterOrBefore(column: ColumnSelector<T, *>, isAfter: Boolean): DataFrame<T> {
2529
val removeResult = df.removeImpl(columns = columns)
2630

@@ -35,8 +39,9 @@ internal fun <T, C> MoveClause<T, C>.afterOrBefore(column: ColumnSelector<T, *>,
3539
if (sourceSegments.size <= targetSegments.size &&
3640
sourceSegments.indices.all { targetSegments[it] == sourceSegments[it] }
3741
) {
42+
val afterOrBefore = if (isAfter) "after" else "before"
3843
throw IllegalArgumentException(
39-
"Cannot move column '${sourcePath.joinToString()}' after its own child column '${targetPath.joinToString()}'",
44+
"Cannot move column '${sourcePath.joinToString()}' $afterOrBefore its own child column '${targetPath.joinToString()}'",
4045
)
4146
}
4247
}
@@ -78,7 +83,16 @@ internal fun <T, C> MoveClause<T, C>.afterOrBefore(column: ColumnSelector<T, *>,
7883
}
7984
ColumnToInsert(path, sourceCol.data, refNode)
8085
}
81-
return removeResult.df.insertImpl(toInsert)
86+
if (isAfter) {
87+
return removeResult.df.insertImpl(toInsert)
88+
}
89+
90+
// Move the target column after the removed/inserted columns
91+
val logicOfAfter = removeResult.df.insertImpl(toInsert)
92+
val lastOfInsertedCols = toInsert.last().insertionPath
93+
val siblingsOfTargetAndTarget = removeResult.df[parentPath].asColumnGroup().columns().map { parentPath + it.path }
94+
val target = siblingsOfTargetAndTarget.filter { it.last() == targetPath.last() }
95+
return logicOfAfter.move { target.toColumnSet() }.after { lastOfInsertedCols }
8296
}
8397

8498
internal fun <T, C> MoveClause<T, C>.moveImpl(

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,72 @@ class MoveTests {
152152
grouped.move { "a"["b"] }.after { "a"["b"] }
153153
}.message shouldBe "Cannot move column 'a/b' after its own child column 'a/b'"
154154
}
155+
156+
@Test
157+
fun `move before first`() {
158+
val df = dataFrameOf("1", "2")(1, 2)
159+
shouldNotThrowAny {
160+
df.move("2").before("1") shouldBe dataFrameOf("2", "1")(2, 1)
161+
}
162+
}
163+
164+
@Test
165+
fun `move before in nested structure`() {
166+
val df = grouped.move { "a"["b"] }
167+
.before { "a"["c"]["d"] }
168+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
169+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
170+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "d")
171+
}
172+
173+
@Test
174+
fun `move before multiple columns`() {
175+
val df = grouped.move { "a"["b"] and "b"["c"] }
176+
.before { "a"["c"]["d"] }
177+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
178+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
179+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "c", "d")
180+
df["b"].asColumnGroup().columnNames() shouldBe listOf("d")
181+
}
182+
183+
@Test
184+
fun `move before with column selector`() {
185+
val df = grouped.move { colsAtAnyDepth().filter { it.name == "r" || it.name == "w" } }
186+
.before { "a"["c"]["d"] }
187+
df.columnNames() shouldBe listOf("q", "a", "b", "e")
188+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("w", "r", "d")
189+
}
190+
191+
@Test
192+
fun `move before between groups`() {
193+
val df = grouped.move { "a"["b"] }.before { "b"["d"] }
194+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
195+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
196+
df["b"].asColumnGroup().columnNames() shouldBe listOf("c", "b", "d")
197+
}
198+
199+
@Test
200+
fun `should throw when moving parent before child`() {
201+
// Simple case: direct parent-child relationship
202+
shouldThrow<IllegalArgumentException> {
203+
grouped.move("a").before { "a"["b"] }
204+
}.message shouldBe "Cannot move column 'a' before its own child column 'a/b'"
205+
206+
// Nested case: deeper parent-child relationship
207+
shouldThrow<IllegalArgumentException> {
208+
grouped.move("a").before { "a"["c"]["d"] }
209+
}.message shouldBe "Cannot move column 'a' before its own child column 'a/c/d'"
210+
211+
// Group case: moving group after its nested column
212+
shouldThrow<IllegalArgumentException> {
213+
grouped.move { "a"["c"] }.before { "a"["c"]["d"] }
214+
}.message shouldBe "Cannot move column 'a/c' before its own child column 'a/c/d'"
215+
}
216+
217+
@Test
218+
fun `should throw when moving column before itself`() {
219+
shouldThrow<IllegalArgumentException> {
220+
grouped.move { "a"["b"] }.before { "a"["b"] }
221+
}.message shouldBe "Cannot move column 'a/b' before its own child column 'a/b'"
222+
}
155223
}

0 commit comments

Comments
 (0)