Skip to content
Open
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
22 changes: 10 additions & 12 deletions src/operations/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,21 @@ export default class FilterDataOperation<T extends object> extends DataOperation
);
}

protected match(record: T, ands: FilterExpression<T>[], ors: FilterExpression<T>[]): boolean {
for (const or of ors) {
if (this.resolveFilter(record, or)) {
return this.match(
record,
ands.filter((f) => f.key !== or.key),
[]
);
}
protected matchTree(record: T, ors: FilterExpression<T>[], ands: FilterExpression<T>[]): boolean {
if (ors.length > 0 && ors.some((expr) => this.resolveFilter(record, expr))) {
return true;
}
return ands.every((f) => this.resolveFilter(record, f));
return ands.every((expr) => this.resolveFilter(record, expr));
}

public apply(data: T[], state: FilterState<T>): T[] {
if (state.empty) return data;

const { ands, ors } = state;
return data.filter((record) => this.match(record, ands, ors));
// Pre-compute ors/ands per tree once rather than re-deriving them per record
const trees = state.values.map((tree) => ({ ors: tree.ors, ands: tree.ands }));

return data.filter((record) =>
trees.every(({ ors, ands }) => this.matchTree(record, ors, ands))
);
}
}
12 changes: 0 additions & 12 deletions src/operations/filter/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,10 @@ export class FilterState<T> {
return this.state.size < 1;
}

public get keys() {
return Array.from(this.state.keys());
}

public get values() {
return Array.from(this.state.values());
}

public get ands() {
return this.values.flatMap((each) => each.ands);
}

public get ors() {
return this.values.flatMap((each) => each.ors);
}

public has(key: Keys<T>) {
return this.state.has(key);
}
Expand Down
39 changes: 23 additions & 16 deletions src/operations/sort.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import DataOperation from './base.js';
import type { SortingExpression, SortState } from './sort/types.js';
import type { SortState } from './sort/types.js';

export default class SortDataOperation<T> extends DataOperation<T> {
protected orderBy = new Map(
Expand All @@ -16,34 +16,41 @@ export default class SortDataOperation<T> extends DataOperation<T> {
return first > second ? 1 : first < second ? -1 : 0;
}

protected compareObjects(first: T, second: T, expression: SortingExpression<T>) {
const { direction, key, caseSensitive, comparer } = expression;

const a = this.resolveCase(this.resolveValue(first, key), caseSensitive);
const b = this.resolveCase(this.resolveValue(second, key), caseSensitive);

// TODO: Remove casting as any
return (
this.orderBy.get(direction)! * (comparer?.(a as any, b as any) ?? this.compareValues(a, b))
);
}

public apply(data: T[], state: SortState<T>) {
const expressions = Array.from(state.values());
const length = expressions.length;

data.sort((a, b) => {
// Pre-compute direction multipliers once to avoid Map lookups in the comparator
const multipliers = expressions.map(({ direction }) => this.orderBy.get(direction)!);

// Store as flat tuples [item, key0, key1, ...] to avoid per-row object allocation
// and transform only once before sorting, then extract the original items after sorting.
const transformed = data.map((item) => {
const tuple: unknown[] = new Array(length + 1);
tuple[0] = item;
for (let i = 0; i < length; i++) {
const { key, caseSensitive } = expressions[i];
tuple[i + 1] = this.resolveCase(this.resolveValue(item, key), caseSensitive);
}
return tuple;
});

transformed.sort((a, b) => {
let i = 0;
let result = 0;

while (i < length && !result) {
result = this.compareObjects(a, b, expressions[i]);
const keyA = a[i + 1];
const keyB = b[i + 1];
result =
multipliers[i] *
(expressions[i].comparer?.(keyA as any, keyB as any) ?? this.compareValues(keyA, keyB));
i++;
}

return result;
});

return data;
return transformed.map((tuple) => tuple[0] as T);
}
}
Loading