/** * 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(); 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; } }