From 84b89f74969619b2bc92e99007dc863612b6c0fb Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sat, 27 Sep 2025 09:45:31 +0200 Subject: [PATCH 01/20] ready to implement the solution --- .../jetbrains/kotlinx/dataframe/api/move.kt | 34 +++++++++++++++++++ .../kotlinx/dataframe/impl/api/move.kt | 23 +++++++------ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 1359db525a..0c5f05fd42 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -621,6 +621,40 @@ public fun MoveClause.after(column: KProperty<*>): DataFrame = a // endregion +// region after + +/** + * Moves columns, previously selected with [move] to the position before the + * specified [column] within the [DataFrame]. + * + * Returns a new [DataFrame] with updated columns. + * + * See [Selecting Columns][SelectingColumns]. + * + * For more information: {@include [DocumentationUrls.Move]} + * + * ### This Before Overload + */ +@ExcludeFromSources +internal interface MoveBefore + +/** + * {@include [MoveBefore]} + * @include [SelectingColumns.Dsl] + * + * ### Examples: + * ```kotlin + * df.move { age and weight }.before { surname } + * df.move { cols(3..5) }.before { col(2) } + * ``` + * + * @param [column] A [ColumnSelector] specifying the column + * before which the selected columns will be placed. + */ +@Refine +@Interpretable("MoveBefore0") +public fun MoveClause.before(column: ColumnSelector): DataFrame = afterOrBefore(column, false) + /* TODO: implement 'before' fun MoveColsClause.before(columnPath: ColumnPath) = before { columnPath.toColumnDef() } fun MoveColsClause.before(column: Column) = before { column } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 277563a67d..f6de1361fe 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -20,9 +20,9 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut -// TODO: support 'before' mode + // TODO: support 'before' mode internal fun MoveClause.afterOrBefore(column: ColumnSelector, isAfter: Boolean): DataFrame { - val removeResult = df.removeImpl(columns = columns) + val removeResult = df.removeImpl(columns = columns) //what remains after the removal val targetPath = df.getColumnWithPath(column).path val sourcePaths = removeResult.removedColumns.map { it.toColumnWithPath().path } @@ -41,12 +41,12 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } } - val removeRoot = removeResult.removedColumns.first().getRoot() - - val refNode = removeRoot.getOrPut(targetPath) { - val path = it.asList() + val removeRoot = removeResult.removedColumns.first().getRoot() //first column to insert, a TreeNode (string, depth(int)..) + //finding the first common node between target and inserting + val refNode = removeRoot.getOrPut(targetPath) { //the TreeNode, first node that target and inserting c. have in common, + val path = it.asList() //df if they both at top - // Get parent of a target path + //Get parent of a target path val effectivePath = path.dropLast(1) // Get column name (last segment) @@ -65,11 +65,11 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnPosition(index, false, col) } - + //final step, val parentPath = targetPath.dropLast(1) val toInsert = removeResult.removedColumns.map { val sourceCol = it.toColumnWithPath() - val sourcePath = sourceCol.path + val sourcePath = sourceCol.path //path of each column to insert val path = if (sourcePath.size > 1) { // If source is nested, preserve its structure under the target parent parentPath + sourcePath.last() @@ -78,7 +78,10 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } ColumnToInsert(path, sourceCol.data, refNode) } - return removeResult.df.insertImpl(toInsert) + return removeResult.df.insertImpl(toInsert)//automatically insert after! + //idea: insertImpl(List) automatically insert after columns that share same path untill the common parent + //-> (idea1) rather than removing and than reinserting source columns, i remove and reinsert target! + //OR (idea2) i create a new version of insertImpl } internal fun MoveClause.moveImpl( From b8fdbdf1c48982c4cdd1dcd143cbb76fde2068ec Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sat, 27 Sep 2025 10:55:43 +0200 Subject: [PATCH 02/20] draft1 --- .../kotlinx/dataframe/impl/api/move.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index f6de1361fe..0f9ab8f4f8 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.getColumn import org.jetbrains.kotlinx.dataframe.api.getColumnGroup import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath +import org.jetbrains.kotlinx.dataframe.api.getColumnsWithPaths import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.columns.ColumnPath import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath @@ -20,11 +21,11 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut - // TODO: support 'before' mode +// TODO: support 'before' mode internal fun MoveClause.afterOrBefore(column: ColumnSelector, isAfter: Boolean): DataFrame { - val removeResult = df.removeImpl(columns = columns) //what remains after the removal + val removeResult = if (isAfter) df.removeImpl(columns = columns) else df.removeImpl(columns = column) //what remains after the removal - val targetPath = df.getColumnWithPath(column).path + val targetPath = if (isAfter) df.getColumnWithPath(column).path else df.getColumnsWithPaths(columns).map { it.path } val sourcePaths = removeResult.removedColumns.map { it.toColumnWithPath().path } // Check if any source path is a prefix of the target path @@ -42,8 +43,10 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } val removeRoot = removeResult.removedColumns.first().getRoot() //first column to insert, a TreeNode (string, depth(int)..) - //finding the first common node between target and inserting - val refNode = removeRoot.getOrPut(targetPath) { //the TreeNode, first node that target and inserting c. have in common, + //finding the first common node between target and inserting + val effectivePath = if (isAfter) targetPath else targetPath.first() + effectivePath as ColumnPath + val refNode = removeRoot.getOrPut(effectivePath) { //the TreeNode, first node that target and inserting c. have in common, val path = it.asList() //df if they both at top //Get parent of a target path @@ -79,9 +82,9 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnToInsert(path, sourceCol.data, refNode) } return removeResult.df.insertImpl(toInsert)//automatically insert after! - //idea: insertImpl(List) automatically insert after columns that share same path untill the common parent - //-> (idea1) rather than removing and than reinserting source columns, i remove and reinsert target! - //OR (idea2) i create a new version of insertImpl + //idea: insertImpl(List) automatically insert after columns that share same path untill the common parent + //-> (idea1) rather than removing and than reinserting source columns, i remove and reinsert target! + //OR (idea2) i create a new version of insertImpl } internal fun MoveClause.moveImpl( From e1724f3f37df1b4bf6cd5932b1ecefb69ced0d47 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sat, 27 Sep 2025 11:05:42 +0200 Subject: [PATCH 03/20] each test for after is working --- .../kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 0f9ab8f4f8..d380a11d5a 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -69,7 +69,7 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnPosition(index, false, col) } //final step, - val parentPath = targetPath.dropLast(1) + val parentPath = effectivePath.dropLast(1) val toInsert = removeResult.removedColumns.map { val sourceCol = it.toColumnWithPath() val sourcePath = sourceCol.path //path of each column to insert From 326fac90f9f9d8b623db1d76a61b28ba7b659b63 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sat, 27 Sep 2025 12:54:47 +0200 Subject: [PATCH 04/20] draft 2 --- .../jetbrains/kotlinx/dataframe/api/move.kt | 27 +++++++++++++++++-- .../kotlinx/dataframe/impl/api/move.kt | 13 ++++----- .../jetbrains/kotlinx/dataframe/api/move.kt | 18 +++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 0c5f05fd42..4ae3632818 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -621,7 +621,7 @@ public fun MoveClause.after(column: KProperty<*>): DataFrame = a // endregion -// region after +// region before /** * Moves columns, previously selected with [move] to the position before the @@ -655,12 +655,35 @@ internal interface MoveBefore @Interpretable("MoveBefore0") public fun MoveClause.before(column: ColumnSelector): DataFrame = afterOrBefore(column, false) +/** + * {@include [MoveBefore]} + * @include [SelectingColumns.ColumnNames] + * + * ### Examples: + * ```kotlin + * df.move("age", "weight").after("surname") + * ``` + * @param [column] The [Column Name][String] specifying the column + * after which the selected columns will be placed. + */ +public fun MoveClause.before(column: String): DataFrame = before { column.toColumnAccessor() } + +@Deprecated(DEPRECATED_ACCESS_API) +@AccessApiOverload +public fun MoveClause.before(column: AnyColumnReference): DataFrame = before { column } + +@Deprecated(DEPRECATED_ACCESS_API) +@AccessApiOverload +public fun MoveClause.before(column: KProperty<*>): DataFrame = before { column.toColumnAccessor() } + +// endregion + /* TODO: implement 'before' fun MoveColsClause.before(columnPath: ColumnPath) = before { columnPath.toColumnDef() } fun MoveColsClause.before(column: Column) = before { column } fun MoveColsClause.before(column: KProperty<*>) = before { column.toColumnDef() } fun MoveColsClause.before(column: String) = before { column.toColumnDef() } -fun MoveColsClause.before(column: ColumnSelector) = afterOrBefore(column, false) +fun MoveColsClause.before(column: ColumnSelector) = afterOrBefore(column, false) DONE */ @Deprecated(TO_LEFT, ReplaceWith(TO_LEFT_REPLACE), DeprecationLevel.ERROR) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index d380a11d5a..ee4140289a 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -26,27 +26,28 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, val removeResult = if (isAfter) df.removeImpl(columns = columns) else df.removeImpl(columns = column) //what remains after the removal val targetPath = if (isAfter) df.getColumnWithPath(column).path else df.getColumnsWithPaths(columns).map { it.path } + val effectiveTargetPath = if (isAfter) targetPath else targetPath.first() + effectiveTargetPath as ColumnPath + val sourcePaths = removeResult.removedColumns.map { it.toColumnWithPath().path } // Check if any source path is a prefix of the target path sourcePaths.forEach { sourcePath -> val sourceSegments = sourcePath.toList() - val targetSegments = targetPath.toList() + val targetSegments = effectiveTargetPath.toList() if (sourceSegments.size <= targetSegments.size && sourceSegments.indices.all { targetSegments[it] == sourceSegments[it] } ) { throw IllegalArgumentException( - "Cannot move column '${sourcePath.joinToString()}' after its own child column '${targetPath.joinToString()}'", + "Cannot move column '${sourcePath.joinToString()}' after its own child column '${effectiveTargetPath.joinToString()}'", ) } } val removeRoot = removeResult.removedColumns.first().getRoot() //first column to insert, a TreeNode (string, depth(int)..) //finding the first common node between target and inserting - val effectivePath = if (isAfter) targetPath else targetPath.first() - effectivePath as ColumnPath - val refNode = removeRoot.getOrPut(effectivePath) { //the TreeNode, first node that target and inserting c. have in common, + val refNode = removeRoot.getOrPut(effectiveTargetPath) { //the TreeNode, first node that target and inserting c. have in common, val path = it.asList() //df if they both at top //Get parent of a target path @@ -69,7 +70,7 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnPosition(index, false, col) } //final step, - val parentPath = effectivePath.dropLast(1) + val parentPath = effectiveTargetPath.dropLast(1) val toInsert = removeResult.removedColumns.map { val sourceCol = it.toColumnWithPath() val sourcePath = sourceCol.path //path of each column to insert diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 45edbe5bf8..7e4d01472e 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -152,4 +152,22 @@ class MoveTests { grouped.move { "a"["b"] }.after { "a"["b"] } }.message shouldBe "Cannot move column 'a/b' after its own child column 'a/b'" } + + @Test + fun `move before first`() { + val df = dataFrameOf("1", "2")(1, 2) + shouldNotThrowAny { + df.move("2").before("1") shouldBe dataFrameOf("2", "1")(2, 1) + } + } + + //val columnNames = listOf("q", "a.b", "b.c", "w", "a.c.d", "e.f", "b.d", "r") + @Test + fun `move before in nested structure`() { + val df = grouped.move { "a"["b"] } + .before { "a"["c"]["d"] } + df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r") + df["a"].asColumnGroup().columnNames() shouldBe listOf("c") + //df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "d") + } } From 68cad6a1d55c6941a50f1aaee0765b251be34b65 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sat, 27 Sep 2025 15:42:13 +0200 Subject: [PATCH 05/20] draft 3 --- .../src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 7e4d01472e..d98da7684c 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -168,6 +168,6 @@ class MoveTests { .before { "a"["c"]["d"] } df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r") df["a"].asColumnGroup().columnNames() shouldBe listOf("c") - //df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "d") + df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "d") } } From bd9d7fe4558c2234f39552baf59e0d8c67f363b4 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sun, 28 Sep 2025 09:47:33 +0200 Subject: [PATCH 06/20] implementing target's sons strategy --- .../kotlinx/dataframe/impl/api/move.kt | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index ee4140289a..95314fbc87 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -11,10 +11,12 @@ import org.jetbrains.kotlinx.dataframe.api.getColumn import org.jetbrains.kotlinx.dataframe.api.getColumnGroup import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath import org.jetbrains.kotlinx.dataframe.api.getColumnsWithPaths +import org.jetbrains.kotlinx.dataframe.api.remove import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.columns.ColumnPath import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy +import org.jetbrains.kotlinx.dataframe.columns.toColumnSet import org.jetbrains.kotlinx.dataframe.impl.DataFrameReceiver import org.jetbrains.kotlinx.dataframe.impl.asList import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath @@ -23,34 +25,32 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut // TODO: support 'before' mode internal fun MoveClause.afterOrBefore(column: ColumnSelector, isAfter: Boolean): DataFrame { - val removeResult = if (isAfter) df.removeImpl(columns = columns) else df.removeImpl(columns = column) //what remains after the removal - val targetPath = if (isAfter) df.getColumnWithPath(column).path else df.getColumnsWithPaths(columns).map { it.path } - val effectiveTargetPath = if (isAfter) targetPath else targetPath.first() - effectiveTargetPath as ColumnPath + val removeResult = df.removeImpl(columns = columns) + val targetPath = df.getColumnWithPath(column).path val sourcePaths = removeResult.removedColumns.map { it.toColumnWithPath().path } // Check if any source path is a prefix of the target path sourcePaths.forEach { sourcePath -> val sourceSegments = sourcePath.toList() - val targetSegments = effectiveTargetPath.toList() + val targetSegments = targetPath.toList() if (sourceSegments.size <= targetSegments.size && sourceSegments.indices.all { targetSegments[it] == sourceSegments[it] } ) { throw IllegalArgumentException( - "Cannot move column '${sourcePath.joinToString()}' after its own child column '${effectiveTargetPath.joinToString()}'", + "Cannot move column '${sourcePath.joinToString()}' after its own child column '${targetPath.joinToString()}'", ) } } - val removeRoot = removeResult.removedColumns.first().getRoot() //first column to insert, a TreeNode (string, depth(int)..) - //finding the first common node between target and inserting - val refNode = removeRoot.getOrPut(effectiveTargetPath) { //the TreeNode, first node that target and inserting c. have in common, - val path = it.asList() //df if they both at top + val removeRoot = removeResult.removedColumns.first().getRoot() + + val refNode = removeRoot.getOrPut(targetPath) { + val path = it.asList() - //Get parent of a target path + // Get parent of a target path val effectivePath = path.dropLast(1) // Get column name (last segment) @@ -69,11 +69,11 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnPosition(index, false, col) } - //final step, - val parentPath = effectiveTargetPath.dropLast(1) + + val parentPath = targetPath.dropLast(1) val toInsert = removeResult.removedColumns.map { val sourceCol = it.toColumnWithPath() - val sourcePath = sourceCol.path //path of each column to insert + val sourcePath = sourceCol.path val path = if (sourcePath.size > 1) { // If source is nested, preserve its structure under the target parent parentPath + sourcePath.last() @@ -82,10 +82,19 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } ColumnToInsert(path, sourceCol.data, refNode) } - return removeResult.df.insertImpl(toInsert)//automatically insert after! - //idea: insertImpl(List) automatically insert after columns that share same path untill the common parent - //-> (idea1) rather than removing and than reinserting source columns, i remove and reinsert target! - //OR (idea2) i create a new version of insertImpl + + if (isAfter) + return removeResult.df.insertImpl(toInsert) + + //remove target's parent sons from removeResult + val toRemove = refNode.children.map { it.name } + val withoutTargetSonsAndSourceColumns = removeResult.df.removeImpl { toRemove.toColumnSet() } + + //add SourceColumns + val withoutTargetSons = withoutTargetSonsAndSourceColumns.df.insertImpl(toInsert) + + //add target's parent sons + } internal fun MoveClause.moveImpl( From fb7ebf7a7fbc04ce71cd1d5041c855355c47c94e Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Sun, 28 Sep 2025 12:48:20 +0200 Subject: [PATCH 07/20] before is working like after --- .../kotlinx/dataframe/impl/api/move.kt | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 95314fbc87..f00f94aab4 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -86,14 +86,28 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, if (isAfter) return removeResult.df.insertImpl(toInsert) - //remove target's parent sons from removeResult + //remove ref Node's sons from removeResult val toRemove = refNode.children.map { it.name } - val withoutTargetSonsAndSourceColumns = removeResult.df.removeImpl { toRemove.toColumnSet() } + val withoutRefNodeAndSourceColumns = removeResult.df.removeImpl { toRemove.toColumnSet() } //add SourceColumns - val withoutTargetSons = withoutTargetSonsAndSourceColumns.df.insertImpl(toInsert) + val withoutRefNodeSons = withoutRefNodeAndSourceColumns.df.insertImpl(toInsert) - //add target's parent sons + //add target's parent sons (refNode's sons) + val refNodeSonsToInsert = withoutRefNodeAndSourceColumns.removedColumns.map{ // List>.map.. + val sourceCol = it.toColumnWithPath() + val sourcePath = sourceCol.path + val path = if (sourcePath.size > 1) { + // If source is nested, preserve its structure under the target parent + parentPath + sourcePath.last() + } else { + parentPath + sourceCol.name() + } + ColumnToInsert(path, sourceCol.data, refNode) + } + + //return + return withoutRefNodeSons.insertImpl(refNodeSonsToInsert) } From fee80910e2ad94de592df436c82853be4fb3fe97 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Tue, 30 Sep 2025 18:34:35 +0200 Subject: [PATCH 08/20] ready to try following advice --- .../kotlinx/dataframe/impl/api/move.kt | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index f00f94aab4..48f5602894 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -83,32 +83,7 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, ColumnToInsert(path, sourceCol.data, refNode) } - if (isAfter) - return removeResult.df.insertImpl(toInsert) - - //remove ref Node's sons from removeResult - val toRemove = refNode.children.map { it.name } - val withoutRefNodeAndSourceColumns = removeResult.df.removeImpl { toRemove.toColumnSet() } - - //add SourceColumns - val withoutRefNodeSons = withoutRefNodeAndSourceColumns.df.insertImpl(toInsert) - - //add target's parent sons (refNode's sons) - val refNodeSonsToInsert = withoutRefNodeAndSourceColumns.removedColumns.map{ // List>.map.. - val sourceCol = it.toColumnWithPath() - val sourcePath = sourceCol.path - val path = if (sourcePath.size > 1) { - // If source is nested, preserve its structure under the target parent - parentPath + sourcePath.last() - } else { - parentPath + sourceCol.name() - } - ColumnToInsert(path, sourceCol.data, refNode) - } - - //return - return withoutRefNodeSons.insertImpl(refNodeSonsToInsert) - + return removeResult.df.insertImpl(toInsert) } internal fun MoveClause.moveImpl( From 639762e49337cc975b97940cb1d46db5ff38f508 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Wed, 1 Oct 2025 19:54:48 +0200 Subject: [PATCH 09/20] sortedByDescending --- .../org/jetbrains/kotlinx/dataframe/impl/api/insert.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt index d8a0a6ba3b..5662990990 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt @@ -40,15 +40,18 @@ internal fun insertImpl( ): DataFrame { if (columns.isEmpty()) return df ?: DataFrame.empty().cast() + //starts from 0 val childDepth = depth + 1 + // map : name of column path's piece at 'depth' -> List of columns with same piece at 'depth' val columnsMap = columns.groupBy { it.insertionPath[depth] }.toMutableMap() // map: columnName -> columnsToAdd val newColumns = mutableListOf() // insert new columns under existing df?.columns()?.forEach { - val subTree = columnsMap[it.name()] + val subTree = columnsMap[it.name()] //cols to insert that have in the path the name of the column -> !cols to insert under 'it'! + //sub tree == null -> nothing to insert under this column if (subTree != null) { // assert that new columns go directly under current column so they have longer paths val invalidPath = subTree.firstOrNull { it.insertionPath.size == childDepth } @@ -56,7 +59,7 @@ internal fun insertImpl( val text = invalidPath!!.insertionPath.joinToString(".") "Can not insert column `$text` because column with this path already exists in DataFrame" } - val group = it as? ColumnGroup<*> + val group = it as? ColumnGroup<*> //it as a ColumnGroup, a sub-type of DataFrame! check(group != null) { "Can not insert columns under a column '${it.name()}', because it is not a column group" } @@ -97,7 +100,7 @@ internal fun insertImpl( } else { null } - }.sortedBy { it.first } // sort by insertion index + }.sortedByDescending { it.first } // sort by insertion index val removedSiblings = treeNode?.children var k = 0 // index in 'removedSiblings' list From 1a995e5b2f5ceea58f5fefc5d396b9cf5444cfa3 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Thu, 2 Oct 2025 09:34:14 +0200 Subject: [PATCH 10/20] strayegy: move siblings after --- .../jetbrains/kotlinx/dataframe/impl/api/insert.kt | 2 +- .../org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt index 5662990990..93d2d49528 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt @@ -100,7 +100,7 @@ internal fun insertImpl( } else { null } - }.sortedByDescending { it.first } // sort by insertion index + }.sortedBy { it.first } // sort by insertion index val removedSiblings = treeNode?.children var k = 0 // index in 'removedSiblings' list diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 48f5602894..1a402e7d8c 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -6,11 +6,13 @@ import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.api.ColumnsSelectionDsl import org.jetbrains.kotlinx.dataframe.api.MoveClause +import org.jetbrains.kotlinx.dataframe.api.asColumnGroup import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.getColumn import org.jetbrains.kotlinx.dataframe.api.getColumnGroup import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath import org.jetbrains.kotlinx.dataframe.api.getColumnsWithPaths +import org.jetbrains.kotlinx.dataframe.api.move import org.jetbrains.kotlinx.dataframe.api.remove import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.columns.ColumnPath @@ -19,6 +21,7 @@ import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy import org.jetbrains.kotlinx.dataframe.columns.toColumnSet import org.jetbrains.kotlinx.dataframe.impl.DataFrameReceiver import org.jetbrains.kotlinx.dataframe.impl.asList +import org.jetbrains.kotlinx.dataframe.impl.columns.depth import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut @@ -82,8 +85,14 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } ColumnToInsert(path, sourceCol.data, refNode) } + if (isAfter) + return removeResult.df.insertImpl(toInsert) + + //move (older) after (last of the newest) + val verticalIsCorrect = removeResult.df.insertImpl(toInsert) + val lastOfNewest = toInsert.map { it.column }.last() + val older = removeResult.df.get() - return removeResult.df.insertImpl(toInsert) } internal fun MoveClause.moveImpl( From eb1f516b7e0d016a659c10cf2f1b5b05b5569e2e Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Thu, 2 Oct 2025 10:22:22 +0200 Subject: [PATCH 11/20] move before first is working --- .../kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 1a402e7d8c..4ad028af64 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -6,7 +6,9 @@ import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.api.ColumnsSelectionDsl import org.jetbrains.kotlinx.dataframe.api.MoveClause +import org.jetbrains.kotlinx.dataframe.api.after import org.jetbrains.kotlinx.dataframe.api.asColumnGroup +import org.jetbrains.kotlinx.dataframe.api.before import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.getColumn import org.jetbrains.kotlinx.dataframe.api.getColumnGroup @@ -91,7 +93,9 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, //move (older) after (last of the newest) val verticalIsCorrect = removeResult.df.insertImpl(toInsert) val lastOfNewest = toInsert.map { it.column }.last() - val older = removeResult.df.get() + //val older = refNode.children.map { it.name } //is empty + val older = removeResult.df[parentPath].asColumnGroup().columns() + return verticalIsCorrect.move{ older.toColumnSet() }.after(lastOfNewest) } From e7e24db32e8359d7a5bf602f0755c2225146e7b7 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Thu, 2 Oct 2025 16:47:16 +0200 Subject: [PATCH 12/20] first test still working --- .../kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 4ad028af64..c18cc78189 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -27,6 +27,8 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.depth import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut +import org.jetbrains.kotlinx.dataframe.path +import org.jetbrains.kotlinx.dataframe.values // TODO: support 'before' mode internal fun MoveClause.afterOrBefore(column: ColumnSelector, isAfter: Boolean): DataFrame { @@ -92,9 +94,9 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, //move (older) after (last of the newest) val verticalIsCorrect = removeResult.df.insertImpl(toInsert) - val lastOfNewest = toInsert.map { it.column }.last() + val lastOfNewest = toInsert.last().insertionPath //val older = refNode.children.map { it.name } //is empty - val older = removeResult.df[parentPath].asColumnGroup().columns() + val older = removeResult.df[parentPath].asColumnGroup().columns() //is path complete? NO return verticalIsCorrect.move{ older.toColumnSet() }.after(lastOfNewest) } From e78b1009eae3d4aa829d339864ee1ea2f286c87b Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Thu, 2 Oct 2025 17:08:52 +0200 Subject: [PATCH 13/20] everything is working but `move before between groups2` --- .../kotlinx/dataframe/impl/api/move.kt | 3 +- .../jetbrains/kotlinx/dataframe/api/move.kt | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index c18cc78189..fa05771d15 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -97,7 +97,8 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, val lastOfNewest = toInsert.last().insertionPath //val older = refNode.children.map { it.name } //is empty val older = removeResult.df[parentPath].asColumnGroup().columns() //is path complete? NO - return verticalIsCorrect.move{ older.toColumnSet() }.after(lastOfNewest) + val olderPaths = older.map { parentPath + it.path } + return verticalIsCorrect.move{ olderPaths.toColumnSet() }.after(lastOfNewest) } diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index d98da7684c..2d77ece570 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -170,4 +170,55 @@ class MoveTests { df["a"].asColumnGroup().columnNames() shouldBe listOf("c") df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "d") } + + @Test + fun `move before multiple columns`() { + val df = grouped.move { "a"["b"] and "b"["c"] } + .before { "a"["c"]["d"] } + df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r") + df["a"].asColumnGroup().columnNames() shouldBe listOf("c") + df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("b", "c", "d") + df["b"].asColumnGroup().columnNames() shouldBe listOf("d") + } + + @Test + fun `move before with column selector`() { + val df = grouped.move { colsAtAnyDepth().filter { it.name == "r" || it.name == "w" } } + .before { "a"["c"]["d"] } + df.columnNames() shouldBe listOf("q", "a", "b", "e") + df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("w", "r", "d") + } + + @Test + fun `move before between groups2`() { + val df = grouped.move { "a"["b"] }.before { "b"["d"] } + df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r") + df["a"].asColumnGroup().columnNames() shouldBe listOf("c") + df["b"].asColumnGroup().columnNames() shouldBe listOf("c", "b", "d") + } + + @Test + fun `should throw when moving parent before child`() { + // Simple case: direct parent-child relationship + shouldThrow { + grouped.move("a").before { "a"["b"] } + }.message shouldBe "Cannot move column 'a' after its own child column 'a/b'" + + // Nested case: deeper parent-child relationship + shouldThrow { + grouped.move("a").before { "a"["c"]["d"] } + }.message shouldBe "Cannot move column 'a' after its own child column 'a/c/d'" + + // Group case: moving group after its nested column + shouldThrow { + grouped.move { "a"["c"] }.before { "a"["c"]["d"] } + }.message shouldBe "Cannot move column 'a/c' after its own child column 'a/c/d'" + } + + @Test + fun `should throw when moving column before itself`() { + shouldThrow { + grouped.move { "a"["b"] }.before { "a"["b"] } + }.message shouldBe "Cannot move column 'a/b' after its own child column 'a/b'" + } } From 7a23bc7375ea61eccb2396abd83f4e5f518b6ffa Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Fri, 3 Oct 2025 09:26:27 +0200 Subject: [PATCH 14/20] each test works --- .../kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 5 ++--- .../test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index fa05771d15..4b4d052938 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -95,9 +95,8 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, //move (older) after (last of the newest) val verticalIsCorrect = removeResult.df.insertImpl(toInsert) val lastOfNewest = toInsert.last().insertionPath - //val older = refNode.children.map { it.name } //is empty - val older = removeResult.df[parentPath].asColumnGroup().columns() //is path complete? NO - val olderPaths = older.map { parentPath + it.path } + val older = removeResult.df[parentPath].asColumnGroup().columns() + val olderPaths = older.map { parentPath + it.path }.filter { it.last() == targetPath.last() } return verticalIsCorrect.move{ olderPaths.toColumnSet() }.after(lastOfNewest) } diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 2d77ece570..ff83367efb 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -190,7 +190,7 @@ class MoveTests { } @Test - fun `move before between groups2`() { + fun `move before between groups`() { val df = grouped.move { "a"["b"] }.before { "b"["d"] } df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r") df["a"].asColumnGroup().columnNames() shouldBe listOf("c") From 10090896b00deaee9c9bf6417b34072b27f76468 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Fri, 3 Oct 2025 09:44:40 +0200 Subject: [PATCH 15/20] cleaning --- .../jetbrains/kotlinx/dataframe/impl/api/move.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index 4b4d052938..e91dee6dfb 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -92,12 +92,13 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, if (isAfter) return removeResult.df.insertImpl(toInsert) - //move (older) after (last of the newest) - val verticalIsCorrect = removeResult.df.insertImpl(toInsert) - val lastOfNewest = toInsert.last().insertionPath - val older = removeResult.df[parentPath].asColumnGroup().columns() - val olderPaths = older.map { parentPath + it.path }.filter { it.last() == targetPath.last() } - return verticalIsCorrect.move{ olderPaths.toColumnSet() }.after(lastOfNewest) + //move target after last of toInsert + val logicOfAfter = removeResult.df.insertImpl(toInsert) + //val lastOfInsertedCols = toInsert.last().insertionPath + val lastOfInsertedCols = toInsert.last().insertionPath + val siblingsOfTargetAndTarget = removeResult.df[parentPath].asColumnGroup().columns().map { parentPath + it.path } + val target = siblingsOfTargetAndTarget.filter { it.last() == targetPath.last() } + return logicOfAfter.move{ target.toColumnSet() }.after{ lastOfInsertedCols } } From a44b15ac5ca3576540b01429b1e79f90a77d2462 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Fri, 3 Oct 2025 09:53:40 +0200 Subject: [PATCH 16/20] cleaning2 --- .../kotlinx/dataframe/impl/api/insert.kt | 7 ++----- .../jetbrains/kotlinx/dataframe/impl/api/move.kt | 16 ++++------------ .../org/jetbrains/kotlinx/dataframe/api/move.kt | 3 +-- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt index 93d2d49528..d8a0a6ba3b 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/insert.kt @@ -40,18 +40,15 @@ internal fun insertImpl( ): DataFrame { if (columns.isEmpty()) return df ?: DataFrame.empty().cast() - //starts from 0 val childDepth = depth + 1 - // map : name of column path's piece at 'depth' -> List of columns with same piece at 'depth' val columnsMap = columns.groupBy { it.insertionPath[depth] }.toMutableMap() // map: columnName -> columnsToAdd val newColumns = mutableListOf() // insert new columns under existing df?.columns()?.forEach { - val subTree = columnsMap[it.name()] //cols to insert that have in the path the name of the column -> !cols to insert under 'it'! - //sub tree == null -> nothing to insert under this column + val subTree = columnsMap[it.name()] if (subTree != null) { // assert that new columns go directly under current column so they have longer paths val invalidPath = subTree.firstOrNull { it.insertionPath.size == childDepth } @@ -59,7 +56,7 @@ internal fun insertImpl( val text = invalidPath!!.insertionPath.joinToString(".") "Can not insert column `$text` because column with this path already exists in DataFrame" } - val group = it as? ColumnGroup<*> //it as a ColumnGroup, a sub-type of DataFrame! + val group = it as? ColumnGroup<*> check(group != null) { "Can not insert columns under a column '${it.name()}', because it is not a column group" } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index e91dee6dfb..ca6e0533f9 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -8,14 +8,11 @@ import org.jetbrains.kotlinx.dataframe.api.ColumnsSelectionDsl import org.jetbrains.kotlinx.dataframe.api.MoveClause import org.jetbrains.kotlinx.dataframe.api.after import org.jetbrains.kotlinx.dataframe.api.asColumnGroup -import org.jetbrains.kotlinx.dataframe.api.before import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.getColumn import org.jetbrains.kotlinx.dataframe.api.getColumnGroup import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath -import org.jetbrains.kotlinx.dataframe.api.getColumnsWithPaths import org.jetbrains.kotlinx.dataframe.api.move -import org.jetbrains.kotlinx.dataframe.api.remove import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.columns.ColumnPath import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath @@ -23,16 +20,12 @@ import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy import org.jetbrains.kotlinx.dataframe.columns.toColumnSet import org.jetbrains.kotlinx.dataframe.impl.DataFrameReceiver import org.jetbrains.kotlinx.dataframe.impl.asList -import org.jetbrains.kotlinx.dataframe.impl.columns.depth import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut import org.jetbrains.kotlinx.dataframe.path -import org.jetbrains.kotlinx.dataframe.values -// TODO: support 'before' mode internal fun MoveClause.afterOrBefore(column: ColumnSelector, isAfter: Boolean): DataFrame { - val removeResult = df.removeImpl(columns = columns) val targetPath = df.getColumnWithPath(column).path @@ -89,17 +82,16 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, } ColumnToInsert(path, sourceCol.data, refNode) } - if (isAfter) + if (isAfter) { return removeResult.df.insertImpl(toInsert) + } - //move target after last of toInsert + // move target after last of toInsert val logicOfAfter = removeResult.df.insertImpl(toInsert) - //val lastOfInsertedCols = toInsert.last().insertionPath val lastOfInsertedCols = toInsert.last().insertionPath val siblingsOfTargetAndTarget = removeResult.df[parentPath].asColumnGroup().columns().map { parentPath + it.path } val target = siblingsOfTargetAndTarget.filter { it.last() == targetPath.last() } - return logicOfAfter.move{ target.toColumnSet() }.after{ lastOfInsertedCols } - + return logicOfAfter.move { target.toColumnSet() }.after { lastOfInsertedCols } } internal fun MoveClause.moveImpl( diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index ff83367efb..2e599eae0e 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -160,8 +160,7 @@ class MoveTests { df.move("2").before("1") shouldBe dataFrameOf("2", "1")(2, 1) } } - - //val columnNames = listOf("q", "a.b", "b.c", "w", "a.c.d", "e.f", "b.d", "r") + @Test fun `move before in nested structure`() { val df = grouped.move { "a"["b"] } From 0202f7b6d45f28291b2d05659d5930ebd3afc92f Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Fri, 3 Oct 2025 10:03:02 +0200 Subject: [PATCH 17/20] cleaning + ran ktlint format --- .../kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 2e599eae0e..955410e832 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -160,7 +160,7 @@ class MoveTests { df.move("2").before("1") shouldBe dataFrameOf("2", "1")(2, 1) } } - + @Test fun `move before in nested structure`() { val df = grouped.move { "a"["b"] } @@ -201,23 +201,23 @@ class MoveTests { // Simple case: direct parent-child relationship shouldThrow { grouped.move("a").before { "a"["b"] } - }.message shouldBe "Cannot move column 'a' after its own child column 'a/b'" + }.message shouldBe "Cannot move column 'a' before its own child column 'a/b'" // Nested case: deeper parent-child relationship shouldThrow { grouped.move("a").before { "a"["c"]["d"] } - }.message shouldBe "Cannot move column 'a' after its own child column 'a/c/d'" + }.message shouldBe "Cannot move column 'a' before its own child column 'a/c/d'" // Group case: moving group after its nested column shouldThrow { grouped.move { "a"["c"] }.before { "a"["c"]["d"] } - }.message shouldBe "Cannot move column 'a/c' after its own child column 'a/c/d'" + }.message shouldBe "Cannot move column 'a/c' before its own child column 'a/c/d'" } @Test fun `should throw when moving column before itself`() { shouldThrow { grouped.move { "a"["b"] }.before { "a"["b"] } - }.message shouldBe "Cannot move column 'a/b' after its own child column 'a/b'" + }.message shouldBe "Cannot move column 'a/b' before its own child column 'a/b'" } } From d365adda999487fe3d35fc5bfd9973746fa63e05 Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Fri, 3 Oct 2025 12:23:31 +0200 Subject: [PATCH 18/20] cleaning --- .../org/jetbrains/kotlinx/dataframe/api/move.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 4ae3632818..54fd5114d0 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -661,10 +661,10 @@ public fun MoveClause.before(column: ColumnSelector): DataFra * * ### Examples: * ```kotlin - * df.move("age", "weight").after("surname") + * df.move("age", "weight").before("surname") * ``` * @param [column] The [Column Name][String] specifying the column - * after which the selected columns will be placed. + * before which the selected columns will be placed. */ public fun MoveClause.before(column: String): DataFrame = before { column.toColumnAccessor() } @@ -678,14 +678,6 @@ public fun MoveClause.before(column: KProperty<*>): DataFrame = // endregion -/* TODO: implement 'before' -fun MoveColsClause.before(columnPath: ColumnPath) = before { columnPath.toColumnDef() } -fun MoveColsClause.before(column: Column) = before { column } -fun MoveColsClause.before(column: KProperty<*>) = before { column.toColumnDef() } -fun MoveColsClause.before(column: String) = before { column.toColumnDef() } -fun MoveColsClause.before(column: ColumnSelector) = afterOrBefore(column, false) DONE -*/ - @Deprecated(TO_LEFT, ReplaceWith(TO_LEFT_REPLACE), DeprecationLevel.ERROR) public fun MoveClause.toLeft(): DataFrame = to(0) From 512f6b4755bca006f1612a05e7036e29d1d5d51e Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Mon, 6 Oct 2025 21:55:34 +0200 Subject: [PATCH 19/20] fix exception's message + improve doc + remove deprecated overloads --- .../kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt | 8 -------- .../org/jetbrains/kotlinx/dataframe/impl/api/move.kt | 5 +++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index 54fd5114d0..a2e761dfee 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -668,14 +668,6 @@ public fun MoveClause.before(column: ColumnSelector): DataFra */ public fun MoveClause.before(column: String): DataFrame = before { column.toColumnAccessor() } -@Deprecated(DEPRECATED_ACCESS_API) -@AccessApiOverload -public fun MoveClause.before(column: AnyColumnReference): DataFrame = before { column } - -@Deprecated(DEPRECATED_ACCESS_API) -@AccessApiOverload -public fun MoveClause.before(column: KProperty<*>): DataFrame = before { column.toColumnAccessor() } - // endregion @Deprecated(TO_LEFT, ReplaceWith(TO_LEFT_REPLACE), DeprecationLevel.ERROR) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt index ca6e0533f9..7d874eca3a 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt @@ -39,8 +39,9 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, if (sourceSegments.size <= targetSegments.size && sourceSegments.indices.all { targetSegments[it] == sourceSegments[it] } ) { + val afterOrBefore = if (isAfter) "after" else "before" throw IllegalArgumentException( - "Cannot move column '${sourcePath.joinToString()}' after its own child column '${targetPath.joinToString()}'", + "Cannot move column '${sourcePath.joinToString()}' $afterOrBefore its own child column '${targetPath.joinToString()}'", ) } } @@ -86,7 +87,7 @@ internal fun MoveClause.afterOrBefore(column: ColumnSelector, return removeResult.df.insertImpl(toInsert) } - // move target after last of toInsert + // Move the target column after the removed/inserted columns val logicOfAfter = removeResult.df.insertImpl(toInsert) val lastOfInsertedCols = toInsert.last().insertionPath val siblingsOfTargetAndTarget = removeResult.df[parentPath].asColumnGroup().columns().map { parentPath + it.path } From 5839b57c88bade58202e1f436a8642f21aabc23c Mon Sep 17 00:00:00 2001 From: Carlo Maria Proietti Date: Mon, 6 Oct 2025 21:58:18 +0200 Subject: [PATCH 20/20] run apiDump --- core/api/core.api | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/api/core.api b/core/api/core.api index 283e186f54..bfd4654674 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -3398,6 +3398,8 @@ public final class org/jetbrains/kotlinx/dataframe/api/MoveKt { public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun after (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lorg/jetbrains/kotlinx/dataframe/columns/ColumnReference;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun before (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun before (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun into (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun into (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun intoIndexed (Lorg/jetbrains/kotlinx/dataframe/api/MoveClause;Lkotlin/jvm/functions/Function3;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;