2023-10-21 12:03:54 +00:00
|
|
|
import {AnyFunction} from '../types';
|
2023-10-23 14:36:49 +00:00
|
|
|
import {isDebugTest} from '../config';
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-23 14:36:49 +00:00
|
|
|
const isDebug = isDebugTest;
|
2023-11-02 13:38:10 +00:00
|
|
|
const orderReducedBy = 1; // reduction of bigO's order compared to the baseline bigO
|
2023-10-20 17:59:10 +00:00
|
|
|
|
|
|
|
export const magnitude = {
|
2023-11-02 13:38:10 +00:00
|
|
|
CONSTANT: Math.pow(10, 9),
|
|
|
|
LOG_N: Math.pow(10, 8 - orderReducedBy),
|
|
|
|
LINEAR: Math.pow(10, 7 - orderReducedBy),
|
|
|
|
N_LOG_N: Math.pow(10, 4 - orderReducedBy),
|
|
|
|
SQUARED: Math.pow(10, 3 - orderReducedBy),
|
|
|
|
CUBED: Math.pow(10, 2 - orderReducedBy),
|
2023-10-20 17:59:10 +00:00
|
|
|
FACTORIAL: 20 - orderReducedBy
|
|
|
|
};
|
|
|
|
|
|
|
|
export const bigO = {
|
|
|
|
CONSTANT: magnitude.CONSTANT / 100000,
|
|
|
|
LOG_N: Math.log2(magnitude.LOG_N) / 1000,
|
|
|
|
LINEAR: magnitude.LINEAR / 1000,
|
|
|
|
N_LOG_N: (magnitude.N_LOG_N * Math.log2(magnitude.LOG_N)) / 1000,
|
|
|
|
SQUARED: Math.pow(magnitude.SQUARED, 2) / 1000,
|
|
|
|
CUBED: Math.pow(magnitude.SQUARED, 3) / 1000,
|
|
|
|
FACTORIAL: 10000
|
|
|
|
};
|
|
|
|
|
|
|
|
function findPotentialN(input: any): number {
|
|
|
|
let longestArray: any[] = [];
|
2023-10-30 03:14:41 +00:00
|
|
|
let mostProperties: {[key: string]: any} = {};
|
2023-10-20 17:59:10 +00:00
|
|
|
|
|
|
|
function recurse(obj: any) {
|
|
|
|
if (Array.isArray(obj)) {
|
|
|
|
if (obj.length > longestArray.length) {
|
|
|
|
longestArray = obj;
|
|
|
|
}
|
|
|
|
} else if (typeof obj === 'object' && obj !== null) {
|
|
|
|
const keys = Object.keys(obj);
|
|
|
|
if (keys.length > Object.keys(mostProperties).length) {
|
|
|
|
mostProperties = obj;
|
|
|
|
}
|
2023-10-21 12:03:54 +00:00
|
|
|
keys.forEach(key => {
|
2023-10-20 17:59:10 +00:00
|
|
|
recurse(obj[key]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(input)) {
|
2023-10-21 12:03:54 +00:00
|
|
|
input.forEach(item => {
|
2023-10-20 17:59:10 +00:00
|
|
|
recurse(item);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
recurse(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return [longestArray, mostProperties] : [any[], { [key: string]: any }];
|
|
|
|
return Math.max(longestArray.length, Object.keys(mostProperties).length);
|
|
|
|
}
|
|
|
|
|
|
|
|
function linearRegression(x: number[], y: number[]) {
|
|
|
|
const n = x.length;
|
|
|
|
|
2023-10-30 12:27:08 +00:00
|
|
|
const sumX = x.reduce((acc, value) => acc + value, 0);
|
|
|
|
const sumY = y.reduce((acc, value) => acc + value, 0);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-30 12:27:08 +00:00
|
|
|
const sumXSquared = x.reduce((acc, value) => acc + value ** 2, 0);
|
|
|
|
const sumXY = x.reduce((acc, value, i) => acc + value * y[i], 0);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
|
|
|
const slope = (n * sumXY - sumX * sumY) / (n * sumXSquared - sumX ** 2);
|
|
|
|
const intercept = (sumY - slope * sumX) / n;
|
|
|
|
|
2023-10-30 12:27:08 +00:00
|
|
|
const yHat = x.map(value => slope * value + intercept);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-30 12:27:08 +00:00
|
|
|
const totalVariation = y.map((value, i) => (value - yHat[i]) ** 2).reduce((acc, value) => acc + value, 0);
|
|
|
|
const explainedVariation = y.map(value => (value - sumY / n) ** 2).reduce((acc, value) => acc + value, 0);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
|
|
|
const rSquared = 1 - totalVariation / explainedVariation;
|
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
return {slope, intercept, rSquared};
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function estimateBigO(runtimes: number[], dataSizes: number[]): string {
|
|
|
|
// Make sure the input runtimes and data sizes have the same length
|
|
|
|
if (runtimes.length !== dataSizes.length) {
|
2023-10-21 12:03:54 +00:00
|
|
|
return 'Lengths of input arrays do not match';
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create an array to store the computational complexity of each data point
|
|
|
|
const complexities: string[] = [];
|
|
|
|
|
|
|
|
// Traverse different possible complexities
|
|
|
|
const complexitiesToCheck: string[] = [
|
2023-10-21 12:03:54 +00:00
|
|
|
'O(1)', // constant time complexity
|
|
|
|
'O(log n)', // Logarithmic time complexity
|
|
|
|
'O(n)', // linear time complexity
|
|
|
|
'O(n log n)', // linear logarithmic time complexity
|
|
|
|
'O(n^2)' // squared time complexity
|
2023-10-20 17:59:10 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
for (const complexity of complexitiesToCheck) {
|
2023-10-21 12:03:54 +00:00
|
|
|
// Calculate data points for fitting
|
|
|
|
const fittedData: number[] = dataSizes.map(size => {
|
|
|
|
if (complexity === 'O(1)') {
|
2023-10-20 17:59:10 +00:00
|
|
|
return 1; // constant time complexity
|
2023-10-21 12:03:54 +00:00
|
|
|
} else if (complexity === 'O(log n)') {
|
2023-10-20 17:59:10 +00:00
|
|
|
return Math.log(size);
|
2023-10-21 12:03:54 +00:00
|
|
|
} else if (complexity === 'O(n)') {
|
2023-10-20 17:59:10 +00:00
|
|
|
return size;
|
2023-10-21 12:03:54 +00:00
|
|
|
} else if (complexity === 'O(n log n)') {
|
2023-10-20 17:59:10 +00:00
|
|
|
return size * Math.log(size);
|
2023-10-21 12:03:54 +00:00
|
|
|
} else if (complexity === 'O(n^2)') {
|
2023-10-20 17:59:10 +00:00
|
|
|
return size ** 2;
|
|
|
|
} else {
|
2023-10-21 12:03:54 +00:00
|
|
|
return size ** 10;
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
// Fit the data points using linear regression analysis
|
2023-10-20 17:59:10 +00:00
|
|
|
const regressionResult = linearRegression(fittedData, runtimes);
|
|
|
|
|
|
|
|
// Check the R-squared value of the fit. It is usually considered a valid fit if it is greater than 0.9.
|
|
|
|
if (regressionResult.rSquared >= 0.9) {
|
|
|
|
complexities.push(complexity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no valid fitting result, return "cannot estimate", otherwise return the estimated time complexity
|
|
|
|
if (complexities.length === 0) {
|
2023-10-21 12:03:54 +00:00
|
|
|
return 'Unable to estimate';
|
2023-10-20 17:59:10 +00:00
|
|
|
} else {
|
2023-10-21 12:03:54 +00:00
|
|
|
return complexities.join(' or ');
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
const methodLogs: Map<string, [number, number][]> = new Map();
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-21 06:46:41 +00:00
|
|
|
export function logBigOMetricsWrap<F extends AnyFunction>(fn: F, args: Parameters<F>, fnName: string) {
|
2023-10-21 12:03:54 +00:00
|
|
|
const startTime = performance.now();
|
|
|
|
const result = fn(args);
|
|
|
|
const endTime = performance.now();
|
|
|
|
const runTime = endTime - startTime;
|
|
|
|
const methodName = `${fnName}`;
|
|
|
|
if (!methodLogs.has(methodName)) {
|
|
|
|
methodLogs.set(methodName, []);
|
|
|
|
}
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
const methodLog = methodLogs.get(methodName);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
const maxDataSize = args.length === 1 && typeof args[0] === 'number' ? args[0] : findPotentialN(args);
|
|
|
|
if (methodLog) {
|
|
|
|
methodLog.push([runTime, maxDataSize]);
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
if (methodLog.length >= 20) {
|
2023-10-21 16:00:22 +00:00
|
|
|
isDebug && console.log('triggered', methodName, methodLog);
|
2023-10-21 12:03:54 +00:00
|
|
|
const bigO = estimateBigO(
|
|
|
|
methodLog.map(([runTime]) => runTime),
|
|
|
|
methodLog.map(([runTime]) => runTime)
|
|
|
|
);
|
2023-10-21 16:00:22 +00:00
|
|
|
isDebug && console.log(`Estimated Big O: ${bigO}`);
|
2023-10-21 12:03:54 +00:00
|
|
|
methodLogs.delete(methodName);
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
2023-10-21 12:03:54 +00:00
|
|
|
}
|
2023-10-20 17:59:10 +00:00
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
return result;
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
|
2023-10-21 06:46:41 +00:00
|
|
|
export function logBigOMetrics(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
|
|
const originalMethod = descriptor.value;
|
|
|
|
|
|
|
|
descriptor.value = function (...args: any[]) {
|
2023-10-20 17:59:10 +00:00
|
|
|
const startTime = performance.now();
|
2023-10-21 06:46:41 +00:00
|
|
|
const result = originalMethod.apply(this, args);
|
2023-10-20 17:59:10 +00:00
|
|
|
const endTime = performance.now();
|
|
|
|
const runTime = endTime - startTime;
|
2023-10-21 06:46:41 +00:00
|
|
|
|
|
|
|
const methodName = `${target.constructor.name}.${propertyKey}`;
|
2023-10-20 17:59:10 +00:00
|
|
|
if (!methodLogs.has(methodName)) {
|
|
|
|
methodLogs.set(methodName, []);
|
|
|
|
}
|
|
|
|
|
|
|
|
const methodLog = methodLogs.get(methodName);
|
|
|
|
|
2023-10-21 12:03:54 +00:00
|
|
|
const maxDataSize = args.length === 1 && typeof args[0] === 'number' ? args[0] : findPotentialN(args);
|
2023-10-20 17:59:10 +00:00
|
|
|
if (methodLog) {
|
|
|
|
methodLog.push([runTime, maxDataSize]);
|
|
|
|
|
|
|
|
if (methodLog.length >= 20) {
|
2023-10-21 16:00:22 +00:00
|
|
|
isDebug && console.log('triggered', methodName, methodLog);
|
2023-10-21 12:03:54 +00:00
|
|
|
const bigO = estimateBigO(
|
|
|
|
methodLog.map(([runTime]) => runTime),
|
|
|
|
methodLog.map(([runTime]) => runTime)
|
|
|
|
);
|
2023-10-21 16:00:22 +00:00
|
|
|
isDebug && console.log(`Estimated Big O: ${bigO}`);
|
2023-10-21 12:03:54 +00:00
|
|
|
methodLogs.delete(methodName);
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2023-10-21 06:46:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return descriptor;
|
2023-10-20 17:59:10 +00:00
|
|
|
}
|