/** * GPA预测算法 - 多项式回归 */ /** * 多项式回归预测 * @param {Array} data - 历史GPA数据 [{semester: '2023-1', gpa: 3.5}, ...] * @param {Number} degree - 多项式阶数,默认2(二次) * @param {Number} future - 预测未来几个学期 * @returns {Object} 预测结果 */ function polynomialRegression(data, degree = 2, future = 3) { if (!data || data.length < 2) { return { predictions: [], trend: 0, confidence: 0 }; } // 提取数据点 const x = data.map((_, index) => index); // 学期编号 [0, 1, 2, ...] const y = data.map(item => item.gpa); // GPA值 // 构建范德蒙德矩阵并求解系数 const coefficients = fitPolynomial(x, y, degree); // 预测未来学期 const predictions = []; const startIndex = data.length; for (let i = 0; i < future; i++) { const xValue = startIndex + i; const yValue = evaluatePolynomial(coefficients, xValue); // 限制GPA在合理范围内 [0, 4.0] const predictedGPA = Math.max(0, Math.min(4.0, yValue)); predictions.push({ semester: generateSemesterName(data.length + i + 1), gpa: Number(predictedGPA.toFixed(2)), isPrediction: true }); } // 计算趋势(与当前学期对比) const currentGPA = y[y.length - 1]; const nextGPA = predictions[0].gpa; const trend = ((nextGPA - currentGPA) / currentGPA * 100).toFixed(1); // 计算置信度(基于R²) const rSquared = calculateRSquared(x, y, coefficients); const confidence = Math.round(rSquared * 100); return { predictions, trend: Number(trend), confidence, coefficients }; } /** * 拟合多项式 - 使用最小二乘法 */ function fitPolynomial(x, y, degree) { const n = x.length; const m = degree + 1; // 构建设计矩阵 X 和目标向量 Y const X = []; for (let i = 0; i < n; i++) { const row = []; for (let j = 0; j <= degree; j++) { row.push(Math.pow(x[i], j)); } X.push(row); } // 计算 X^T * X const XTX = multiplyMatrices(transpose(X), X); // 计算 X^T * Y const XTY = multiplyMatrixVector(transpose(X), y); // 求解方程 (X^T * X) * coefficients = X^T * Y const coefficients = solveLinearSystem(XTX, XTY); return coefficients; } /** * 计算多项式值 */ function evaluatePolynomial(coefficients, x) { let result = 0; for (let i = 0; i < coefficients.length; i++) { result += coefficients[i] * Math.pow(x, i); } return result; } /** * 计算R²(决定系数) */ function calculateRSquared(x, y, coefficients) { const n = x.length; const yMean = y.reduce((sum, val) => sum + val, 0) / n; let ssTotal = 0; let ssResidual = 0; for (let i = 0; i < n; i++) { const yPred = evaluatePolynomial(coefficients, x[i]); ssTotal += Math.pow(y[i] - yMean, 2); ssResidual += Math.pow(y[i] - yPred, 2); } return 1 - (ssResidual / ssTotal); } /** * 矩阵转置 */ function transpose(matrix) { const rows = matrix.length; const cols = matrix[0].length; const result = []; for (let j = 0; j < cols; j++) { const row = []; for (let i = 0; i < rows; i++) { row.push(matrix[i][j]); } result.push(row); } return result; } /** * 矩阵乘法 */ function multiplyMatrices(a, b) { const rowsA = a.length; const colsA = a[0].length; const colsB = b[0].length; const result = []; for (let i = 0; i < rowsA; i++) { const row = []; for (let j = 0; j < colsB; j++) { let sum = 0; for (let k = 0; k < colsA; k++) { sum += a[i][k] * b[k][j]; } row.push(sum); } result.push(row); } return result; } /** * 矩阵向量乘法 */ function multiplyMatrixVector(matrix, vector) { const rows = matrix.length; const result = []; for (let i = 0; i < rows; i++) { let sum = 0; for (let j = 0; j < vector.length; j++) { sum += matrix[i][j] * vector[j]; } result.push(sum); } return result; } /** * 求解线性方程组 - 高斯消元法 */ function solveLinearSystem(A, b) { const n = A.length; const augmented = A.map((row, i) => [...row, b[i]]); // 前向消元 for (let i = 0; i < n; i++) { // 选主元 let maxRow = i; for (let k = i + 1; k < n; k++) { if (Math.abs(augmented[k][i]) > Math.abs(augmented[maxRow][i])) { maxRow = k; } } // 交换行 [augmented[i], augmented[maxRow]] = [augmented[maxRow], augmented[i]]; // 消元 for (let k = i + 1; k < n; k++) { const factor = augmented[k][i] / augmented[i][i]; for (let j = i; j <= n; j++) { augmented[k][j] -= factor * augmented[i][j]; } } } // 回代 const x = new Array(n); for (let i = n - 1; i >= 0; i--) { x[i] = augmented[i][n]; for (let j = i + 1; j < n; j++) { x[i] -= augmented[i][j] * x[j]; } x[i] /= augmented[i][i]; } return x; } /** * 生成学期名称 */ function generateSemesterName(index) { const year = 2023 + Math.floor(index / 2); const term = index % 2 === 1 ? '1' : '2'; return `${year}-${term}`; } /** * 计算GPA(根据分数) * @param {Number} score - 分数 * @returns {Number} GPA绩点 */ function calculateGPA(score) { if (score >= 60) { return (score / 10 - 5); } return 0; } /** * 批量计算GPA */ function batchCalculateGPA(courses) { return courses.map(course => ({ ...course, gpa: calculateGPA(course.score) })); } /** * 计算平均GPA */ function calculateAverageGPA(courses) { if (!courses || courses.length === 0) return 0; const totalCredits = courses.reduce((sum, c) => sum + (c.credit || 1), 0); const weightedSum = courses.reduce((sum, c) => { const gpa = c.gpa || calculateGPA(c.score); return sum + gpa * (c.credit || 1); }, 0); return (weightedSum / totalCredits).toFixed(2); } module.exports = { polynomialRegression, calculateGPA, batchCalculateGPA, calculateAverageGPA };