Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): optimize split into grid function #3565

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 78 additions & 22 deletions packages/core/src/shared/__tests__/range.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { AbsoluteRefType } from '../../sheets/typedef';
import { mergeHorizontalRanges, mergeRanges, mergeVerticalRanges, moveRangeByOffset, splitIntoGrid } from '../range';
import type { IRange } from '../../sheets/typedef';

const stringifyRanges = (ranges: IRange[]) => {
return ranges.sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn).map((range) => `${range.startRow},${range.startColumn},${range.endRow},${range.endColumn}`);
};

describe('test moveRangeByOffset', () => {
it('test normal', () => {
const range = {
Expand Down Expand Up @@ -92,13 +96,13 @@ describe('splitIntoGrid', () => {
{ startColumn: 6, endColumn: 8, startRow: 2, endRow: 2 },
];

expect(splitIntoGrid(input)).toEqual(expected);
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle no ranges', () => {
const input: IRange[] = [];
const expected: IRange[] = [];
expect(splitIntoGrid(input)).toEqual(expected);
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle single range', () => {
Expand All @@ -108,7 +112,18 @@ describe('splitIntoGrid', () => {
const expected: IRange[] = [
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 },
];
expect(splitIntoGrid(input)).toEqual(expected);
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle full overlap range', () => {
const input: IRange[] = [
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 },
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 },
];
const expected: IRange[] = [
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 },
];
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle simple case', () => {
Expand All @@ -119,7 +134,7 @@ describe('splitIntoGrid', () => {
{ startColumn: 6, endColumn: 8, startRow: 2, endRow: 2 },
];

expect(splitIntoGrid(input)).toEqual(expected);
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle this case', () => {
Expand All @@ -144,7 +159,48 @@ describe('splitIntoGrid', () => {
{ startColumn: 6, endColumn: 7, startRow: 8, endRow: 8 },
{ startColumn: 0, endColumn: 5, startRow: 9, endRow: 10 },
];
expect(JSON.stringify(splitIntoGrid(input))).toEqual(JSON.stringify(expected));

expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle small range', () => {
const input = [{ startColumn: 1, endColumn: 1, startRow: 1, endRow: 1 }, { startColumn: 1, endColumn: 1, startRow: 2, endRow: 2 }, { startColumn: 2, endColumn: 2, startRow: 1, endRow: 1 }, { startColumn: 2, endColumn: 2, startRow: 2, endRow: 2 }, { startColumn: 1, endColumn: 1, startRow: 2, endRow: 2 }];
const expected = [{ startColumn: 1, endColumn: 1, startRow: 1, endRow: 1 }, { startColumn: 1, endColumn: 1, startRow: 2, endRow: 2 }, { startColumn: 2, endColumn: 2, startRow: 1, endRow: 1 }, { startColumn: 2, endColumn: 2, startRow: 2, endRow: 2 }];
expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});

it('should handle overlapping ranges correctly', () => {
const input = [
{
startColumn: 1,
endColumn: 4,
startRow: 1,
endRow: 4,
},
{
startColumn: 1,
startRow: 1,
endColumn: 5,
endRow: 4,
},
];

const expected: IRange[] = [
{
startColumn: 1,
endColumn: 4,
startRow: 1,
endRow: 4,
},
{
startColumn: 5,
startRow: 1,
endColumn: 5,
endRow: 4,
},
];

expect(stringifyRanges(splitIntoGrid(input))).toEqual(stringifyRanges(expected));
});
});

Expand All @@ -159,7 +215,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 1, endColumn: 8, startRow: 1, endRow: 1 },
];

expect(mergeHorizontalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle ranges with gaps', () => {
Expand All @@ -173,7 +229,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 5, endColumn: 7, startRow: 1, endRow: 1 },
];

expect(mergeHorizontalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle overlapping ranges', () => {
Expand All @@ -186,7 +242,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 1, endColumn: 8, startRow: 1, endRow: 1 },
];

expect(mergeHorizontalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle multiple rows independently', () => {
Expand All @@ -202,7 +258,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 1, endColumn: 7, startRow: 2, endRow: 2 },
];

expect(mergeHorizontalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle single range', () => {
Expand All @@ -214,7 +270,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 1 },
];

expect(mergeHorizontalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle no ranges', () => {
Expand All @@ -234,7 +290,7 @@ describe('mergeHorizontalRanges', () => {
{ startColumn: 7, endColumn: 7, startRow: 1, endRow: 2 },
];
const expected = [{ startColumn: 1, endColumn: 7, startRow: 1, endRow: 2 }];
expect(JSON.stringify(mergeHorizontalRanges(input))).toEqual(JSON.stringify(expected));
expect(stringifyRanges(mergeHorizontalRanges(input))).toEqual(stringifyRanges(expected));
});
});

Expand Down Expand Up @@ -264,7 +320,7 @@ describe('mergeVerticalRanges', () => {
{ startColumn: 1, endColumn: 1, startRow: 5, endRow: 7 },
];

expect(mergeVerticalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeVerticalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle overlapping ranges', () => {
Expand All @@ -277,7 +333,7 @@ describe('mergeVerticalRanges', () => {
{ startColumn: 1, endColumn: 1, startRow: 1, endRow: 8 },
];

expect(mergeVerticalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeVerticalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle multiple columns independently', () => {
Expand All @@ -293,7 +349,7 @@ describe('mergeVerticalRanges', () => {
{ startColumn: 2, endColumn: 2, startRow: 1, endRow: 8 },
];

expect(mergeVerticalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeVerticalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle single range', () => {
Expand All @@ -305,7 +361,7 @@ describe('mergeVerticalRanges', () => {
{ startColumn: 1, endColumn: 8, startRow: 1, endRow: 2 },
];

expect(mergeVerticalRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeVerticalRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle no ranges', () => {
Expand All @@ -321,7 +377,7 @@ describe('mergeVerticalRanges', () => {
{ startColumn: 0, endColumn: 5, startRow: 9, endRow: 10 },
];

expect(JSON.stringify(mergeVerticalRanges(input))).toEqual(JSON.stringify(input));
expect(stringifyRanges(mergeVerticalRanges(input))).toEqual(stringifyRanges(input));
});
});

Expand All @@ -336,13 +392,13 @@ describe('mergeRanges', () => {
{ startColumn: 1, endColumn: 8, startRow: 1, endRow: 2 },
];

expect(JSON.stringify(mergeRanges(input))).toEqual(JSON.stringify(expected));
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle no ranges', () => {
const input: IRange[] = [];
const expected: IRange[] = [];
expect(mergeRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle single range', () => {
Expand All @@ -354,7 +410,7 @@ describe('mergeRanges', () => {
{ startColumn: 1, endColumn: 3, startRow: 1, endRow: 3 },
];

expect(mergeRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle ranges with gaps', () => {
Expand All @@ -368,7 +424,7 @@ describe('mergeRanges', () => {
{ startColumn: 5, endColumn: 7, startRow: 1, endRow: 2 },
];

expect(mergeRanges(input)).toEqual(expected);
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle multiple ranges in multiple rows and columns', () => {
Expand All @@ -382,7 +438,7 @@ describe('mergeRanges', () => {
{ startColumn: 1, endColumn: 5, startRow: 1, endRow: 5 },
];

expect(JSON.stringify(mergeRanges(input))).toEqual(JSON.stringify(expected));
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});

it('should handle this case', () => {
Expand All @@ -406,6 +462,6 @@ describe('mergeRanges', () => {
{ startColumn: 0, endColumn: 7, startRow: 8, endRow: 8 },
{ startColumn: 0, endColumn: 5, startRow: 9, endRow: 10 },
];
expect(JSON.stringify(mergeRanges(input))).toEqual(JSON.stringify(expected));
expect(stringifyRanges(mergeRanges(input))).toEqual(stringifyRanges(expected));
});
});
3 changes: 3 additions & 0 deletions packages/core/src/shared/object-matrix-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ function resetMatrix<T>(matrix: Nullable<T>[][], range: IRange) {
});
}

/**
* @deprecated this function could cause memory out of use in large range.
*/
export function queryObjectMatrix<T>(matrix: ObjectMatrix<T>, match: (value: T) => boolean) {
const arrayMatrix = matrix.toFullArray();
const results: IRange[] = [];
Expand Down
54 changes: 29 additions & 25 deletions packages/core/src/shared/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,32 +72,37 @@ export function splitIntoGrid(ranges: IRange[]): IRange[] {
const sortedColumns = Array.from(columns).sort((a, b) => a - b);
const sortedRows = Array.from(rows).sort((a, b) => a - b);

// Create grid cells based on unique boundaries
const gridCells: IRange[] = [];
for (let i = 0; i < sortedRows.length - 1; i++) {
for (let j = 0; j < sortedColumns.length - 1; j++) {
gridCells.push({
startColumn: sortedColumns[j],
endColumn: sortedColumns[j + 1] - 1,
startRow: sortedRows[i],
endRow: sortedRows[i + 1] - 1,
});
}
}
// Sort ranges by startRow and startColumn
ranges.sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn);

// Assign original ranges to the grid cells
// Assign original ranges to the grid cells
const result: IRange[] = [];
for (const gridCell of gridCells) {
for (const range of ranges) {
if (range.startRow <= gridCell.endRow && range.endRow >= gridCell.startRow &&
range.startColumn <= gridCell.endColumn && range.endColumn >= gridCell.startColumn) {
result.push({
startColumn: Math.max(gridCell.startColumn, range.startColumn),
endColumn: Math.min(gridCell.endColumn, range.endColumn),
startRow: Math.max(gridCell.startRow, range.startRow),
endRow: Math.min(gridCell.endRow, range.endRow),
});
break; // No need to check other ranges for this grid cell
for (let i = 0; i < sortedRows.length - 1; i++) {
for (let j = 0; j < sortedColumns.length - 1; j++) {
// grid cell
const startColumn = sortedColumns[j];
const endColumn = sortedColumns[j + 1] - 1;
const startRow = sortedRows[i];
const endRow = sortedRows[i + 1] - 1;

for (const range of ranges) {
if (range.startRow > endRow) {
// Since ranges are sorted, we can break early
break;
}

// grid cell must be in some range
// just need to check range is contain grid cell
if (range.startRow <= startRow && range.endRow >= endRow &&
range.startColumn <= startColumn && range.endColumn >= endColumn) {
result.push({
startColumn,
endColumn,
startRow,
endRow,
});
break; // No need to check other ranges for this grid cell
}
}
}
}
Expand Down Expand Up @@ -213,7 +218,6 @@ export function mergeRanges(ranges: IRange[]): IRange[] {
const split = splitIntoGrid(ranges);
const horizontalMerged = mergeHorizontalRanges(split);
return mergeVerticalRanges(horizontalMerged);
// return horizontalMerged;
}

export function multiSubtractSingleRange(ranges: IRange[], toDelete: IRange) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/shared/rectangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export class Rectangle {
res = multiSubtractSingleRange(res, range);
});

return Rectangle.mergeRanges(res);
return res;
}

static hasIntersectionBetweenTwoRect(rect1: IRectLTRB, rect2: IRectLTRB) {
Expand Down
Loading
Loading