From 48dc2dee8829d83eaca8ad4161d6f2d3f336dec0 Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Mon, 20 Jul 2026 01:24:32 +0800 Subject: [PATCH] perf(grid): scan all-null columns once --- .../lib/dataGrid/dataGridColumnVisibility.ts | 17 ++++++++++++++++- .../app-tests/dataGridColumnVisibility.test.ts | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/lib/dataGrid/dataGridColumnVisibility.ts b/apps/desktop/src/lib/dataGrid/dataGridColumnVisibility.ts index 3c855bdc6..d69a1a1d4 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridColumnVisibility.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridColumnVisibility.ts @@ -53,7 +53,22 @@ export function invertedHiddenColumnIndexes(availableIndexes: number[], hiddenIn export function allNullColumnIndexes(rows: ReadonlyArray>, availableIndexes: number[]): number[] { if (rows.length === 0) return []; - return availableIndexes.filter((index) => rows.every((row) => row[index] === null)); + // 单遍行扫描 + 候选原地压缩:列一旦见到非 null 即永久剔除,候选清空提前 + // 返回。最坏复杂度同为 O(列×行)(全 null 时无法避免),改进在于常数项: + // 原实现逐列 rows.every 各自从头扫外层 rows 并逐格调用回调,这里每个 + // 单元格至多访问一次且无闭包调用 + const candidates = [...availableIndexes]; + let count = candidates.length; + for (const row of rows) { + let write = 0; + for (let read = 0; read < count; read++) { + if (row[candidates[read]!] === null) candidates[write++] = candidates[read]!; + } + count = write; + if (count === 0) return []; + } + candidates.length = count; + return candidates; } export function hiddenColumnIndexesWithAllNullColumns(options: { availableIndexes: number[]; hiddenIndexes: ReadonlySet; allNullIndexes: ReadonlySet }): { hiddenIndexes: Set; autoHiddenIndexes: Set } { diff --git a/packages/app-tests/dataGridColumnVisibility.test.ts b/packages/app-tests/dataGridColumnVisibility.test.ts index 7a3f40997..704b581bd 100644 --- a/packages/app-tests/dataGridColumnVisibility.test.ts +++ b/packages/app-tests/dataGridColumnVisibility.test.ts @@ -97,6 +97,22 @@ test("finds columns where every row is NULL", () => { assert.deepEqual(indexes, [1, 2]); }); +test("all-null scan handles sparse, ragged and non-candidate columns", () => { + // 前缀多 null 但尾行非 null 的列必须被剔除;availableIndexes 之外的列不参与 + const rows = [ + [null, 1, null, null], + [null, null, null, null], + [null, null, 2, null], + ]; + assert.deepEqual(allNullColumnIndexes(rows, [0, 1, 2]), [0]); + // 不等宽行:短行缺失的单元格为 undefined ≠ null,剔除该列(与原实现一致) + assert.deepEqual(allNullColumnIndexes([[null, null], [null]], [0, 1]), [0]); + // 全部候选都非 null:提前收敛返回空 + assert.deepEqual(allNullColumnIndexes([[1, 2]], [0, 1]), []); + // 乱序候选:返回顺序保持 availableIndexes 原序 + assert.deepEqual(allNullColumnIndexes([[null, 1, null]], [2, 0]), [2, 0]); +}); + test("does not treat empty results as all-null columns", () => { assert.deepEqual(allNullColumnIndexes([], [0, 1, 2]), []); });