Skip to content

Commit 9bb5e8b

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/allexcept-npe-fix' into all-except-option-2
2 parents 2086e0b + 073e1bb commit 9bb5e8b

File tree

3 files changed

+78
-50
lines changed

3 files changed

+78
-50
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ public interface AllExceptColumnsSelectionDsl {
10231023
internal fun <C> ColumnSet<C>.exceptInternal(other: ColumnsResolver<*>): ColumnSet<C> =
10241024
createColumnSet { context ->
10251025
val resolvedCols = this.resolve(context)
1026-
val resolvedColsToExcept = other.resolve(context)
1026+
val resolvedColsToExcept = other.resolve(context).toSet()
10271027
resolvedCols.allColumnsExceptKeepingStructure(resolvedColsToExcept)
10281028
} as ColumnSet<C>
10291029

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/columns/Utils.kt

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -416,59 +416,66 @@ internal fun List<ColumnWithPath<*>>.allColumnsExceptAndUnpack(
416416
* Empty groups will be removed if [removeEmptyGroups]` == true`
417417
*/
418418
internal fun List<ColumnWithPath<*>>.allColumnsExceptKeepingStructure(
419-
columns: Iterable<ColumnWithPath<*>>,
419+
columns: Set<ColumnWithPath<*>>,
420420
removeEmptyGroups: Boolean = true,
421421
): List<ColumnWithPath<*>> {
422422
if (isEmpty()) return emptyList()
423-
val fullTree = collectTree()
424-
for (columnToExcept in columns) {
425-
// grab the node representing the column from the tree
426-
val nodeToExcept = fullTree.getOrPut(columnToExcept.path).asNullable()
427-
if (nodeToExcept != null) {
428-
// remove the children from the node (if it's a column group) and remove its data (the column itself)
429-
nodeToExcept.allChildren().forEach { it.data = null }
430-
nodeToExcept.data = null
431-
432-
// we need to update the data of the parent node(s) to reflect the removal of the column
433-
if (nodeToExcept.parent != null) {
434-
// we grab the data of the parent node, which should be a column group
435-
// treat it as a DF to remove the column to except from it and
436-
// convert it back to a column group
437-
val current = nodeToExcept.parent.data as ColumnGroup<*>? ?: continue
438-
val adjustedCurrent = current
439-
.remove(nodeToExcept.name)
440-
.asColumnGroup(current.name)
441-
.addPath(current.path())
442-
443-
// remove the group if it's empty and removeEmptyGroups is true
444-
// else, simply update the parent's data with the adjusted column group
445-
nodeToExcept.parent.data =
446-
if (adjustedCurrent.cols().isEmpty() && removeEmptyGroups) {
447-
null
448-
} else {
449-
adjustedCurrent
423+
return flatMap {
424+
val fullTree = listOf(it).collectTree()
425+
for (columnToExcept in columns.sortedByDescending { it.path.size }) {
426+
// grab the node representing the column from the tree
427+
val nodeToExcept = fullTree.getOrPut(columnToExcept.path).asNullable()
428+
if (nodeToExcept != null) {
429+
// remove the children from the node (if it's a column group) and remove its data (the column itself)
430+
nodeToExcept.allChildren().forEach { it.data = null }
431+
nodeToExcept.data = null
432+
433+
// we need to update the data of the parent node(s) to reflect the removal of the column
434+
if (nodeToExcept.parent != null) {
435+
// we grab the data of the parent node, which should be a column group
436+
// treat it as a DF to remove the column to except from it and
437+
// convert it back to a column group
438+
val current = nodeToExcept.parent.data as ColumnGroup<*>? ?: continue
439+
val adjustedCurrent = current
440+
.remove(nodeToExcept.name)
441+
.asColumnGroup(current.name)
442+
.addPath(current.path())
443+
444+
// remove the group if it's empty and removeEmptyGroups is true
445+
// else, simply update the parent's data with the adjusted column group
446+
nodeToExcept.parent.data =
447+
if (adjustedCurrent.cols().isEmpty() && removeEmptyGroups) {
448+
null
449+
} else {
450+
adjustedCurrent
451+
}
452+
453+
// now we update the parent's parents recursively with new column group instances
454+
var parent = nodeToExcept.parent.parent
455+
456+
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
457+
var currentNode = nodeToExcept.parent!!
458+
while (parent != null) {
459+
val parentData = parent.data as ColumnGroup<*>? ?: break
460+
val currentData = currentNode.data
461+
val modifiedParentData =
462+
if (currentData == null) {
463+
parentData.remove(currentNode.name)
464+
} else {
465+
parentData.replace(currentNode.name).with { currentData }
466+
}
467+
parent.data = modifiedParentData
468+
.asColumnGroup(parentData.name)
469+
.addPath(parentData.path())
470+
currentNode = parent
471+
parent = parent.parent
450472
}
451-
452-
// now we update the parent's parents recursively with new column group instances
453-
var parent = nodeToExcept.parent.parent
454-
455-
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
456-
var currentNode = nodeToExcept.parent!!
457-
while (parent != null) {
458-
val parentData = parent.data as ColumnGroup<*>? ?: break
459-
parent.data = parentData
460-
.replace(currentNode.name).with { currentNode.data!! }
461-
.asColumnGroup(parentData.name)
462-
.addPath(parentData.path())
463-
464-
currentNode = parent
465-
parent = parent.parent
466473
}
467474
}
468475
}
476+
val subtrees = fullTree.topmostChildren { it.data != null }
477+
subtrees.map { it.data!!.addPath(it.pathFromRoot()) }
469478
}
470-
val subtrees = fullTree.topmostChildren { it.data != null }
471-
return subtrees.map { it.data!!.addPath(it.pathFromRoot()) }
472479
}
473480

474481
/**

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@ import org.junit.Test
1616

1717
class AllExceptTests : ColumnsSelectionDslTests() {
1818

19+
@Test
20+
fun `issue 761`() {
21+
val renamed = df.rename { colsAtAnyDepth() except name.firstName }.into { it.name.uppercase() }
22+
renamed.columnNames() shouldBe listOf("NAME", "AGE", "CITY", "WEIGHT", "ISHAPPY")
23+
renamed.getColumnGroup("NAME").columnNames() shouldBe listOf("firstName", "LASTNAME")
24+
25+
val df2 = dataFrameOf("a.b", "a.c.d", "d.e", "d.f")(1, 3.0, 2, "b")
26+
.move { all() }.into { it.name.split(".").toPath() }
27+
df2.select { cols("a") except "a"["b"] }.let {
28+
it.getColumnGroup("a").getColumnOrNull("b") shouldBe null
29+
it[pathOf("a", "c", "d")].single() shouldBe 3.0
30+
}
31+
df2.select { cols("a") except "a"["c"]["d"] }.let {
32+
it.getColumnGroup("a").getColumnOrNull("c") shouldBe null
33+
it[pathOf("a", "b")].single() shouldBe 1
34+
}
35+
}
36+
1937
@Test
2038
fun `exceptions`() {
2139
shouldThrow<IllegalStateException> {
@@ -70,12 +88,15 @@ class AllExceptTests : ColumnsSelectionDslTests() {
7088
).shouldAllBeEqual()
7189

7290
listOf(
73-
df.select { name and name.firstName }.alsoDebug(),
91+
df.select { cols(name) except name.firstName },
92+
df.select { (name and name.firstName and name.firstName) except name.firstName },
93+
df.select { (name and name and name.firstName).except(name.firstName).simplify() },
7494
).shouldAllBeEqual()
7595

76-
df.select { (name and name.firstName and name.firstName) except name.firstName }.alsoDebug()
77-
78-
df.select { (name and name and name.firstName) except name.firstName }.alsoDebug()
96+
df.getColumns { (name and name and name.firstName).except(name.firstName) }.forEach {
97+
it.isColumnGroup() shouldBe true
98+
it.asColumnGroup().columnNames() shouldBe listOf("lastName")
99+
}
79100
}
80101

81102
@Test

0 commit comments

Comments
 (0)