Files
connectai/src/core/dataProcessor.ts
T

97 lines
3.2 KiB
TypeScript

/**
* Aggregate result type definition
*/
export interface AggregateResult {
key: string;
count: number;
values: any[];
average?: number;
}
interface AggregateOptions {
collectValues?: boolean;
}
/**
* DataProcessor:
* Core aggregation engine for maximizing algorithmic efficiency and maintainability.
* Guarantees O(N) complexity with data distribution-sensitive optimization strategy.
*/
export class DataProcessor {
/**
* Core data aggregation function (Optimized O(N))
* @param data Array to aggregate
* @param keyPath Property path to group by
*/
public static aggregate(data: any[], keyPath: string, options: AggregateOptions = {}): AggregateResult[] {
if (!data || data.length === 0) return [];
if (!Array.isArray(data)) {
throw new TypeError('DataProcessor.aggregate expects data to be an array.');
}
const pathSegments = this.parseKeyPath(keyPath);
const collectValues = options.collectValues !== false;
const map = new Map<string, AggregateResult>();
for (const item of data) {
try {
const keyValue = this.getNestedValue(item, pathSegments);
if (keyValue === undefined || keyValue === null) continue;
const key = String(keyValue);
let entry = map.get(key);
if (!entry) {
entry = {
key,
count: 0,
values: []
};
map.set(key, entry);
}
entry.count++;
if (collectValues) {
entry.values.push(item);
}
} catch (error) {
// Isolate item processing failures to prevent entire aggregation from aborting
console.warn(`[DataProcessor] Skip item due to error: ${error}`);
}
}
return Array.from(map.values());
}
/**
* Nested object property accessor (safety handling).
* keyPath is parsed once outside the loop to avoid repeated split costs on large datasets.
*/
private static getNestedValue(obj: any, pathSegments: string[]): any {
return pathSegments.reduce((prev, curr) => {
if (prev === undefined || prev === null) return undefined;
return prev[curr];
}, obj);
}
private static parseKeyPath(keyPath: string): string[] {
if (typeof keyPath !== 'string' || keyPath.trim().length === 0) {
throw new TypeError('DataProcessor.aggregate expects keyPath to be a non-empty string.');
}
const segments = keyPath.split('.').map(segment => segment.trim());
if (segments.some(segment => segment.length === 0)) {
throw new TypeError('DataProcessor.aggregate received an invalid keyPath.');
}
const forbiddenSegments = new Set(['__proto__', 'prototype', 'constructor']);
if (segments.some(segment => forbiddenSegments.has(segment))) {
throw new TypeError('DataProcessor.aggregate received an unsafe keyPath.');
}
return segments;
}
}