From 6bb6a5cb93e8c6c7fa88a885802884822e97a4cf Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 5 Mar 2025 16:09:01 +0700 Subject: [PATCH 1/4] feat: add interpolation and regression --- README.md | 41 ++++++++++++++++ src/area.ts | 34 +++++++------ src/index.ts | 2 + src/interpolation.ts | 49 +++++++++++++++++++ src/numbers.ts | 39 +++++++++------ src/regression.ts | 112 +++++++++++++++++++++++++++++++++++++++++++ src/temperatures.ts | 21 +++++--- 7 files changed, 262 insertions(+), 36 deletions(-) create mode 100644 src/interpolation.ts create mode 100644 src/regression.ts diff --git a/README.md b/README.md index 27520fe..2b6129a 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,47 @@ Convert between Celsius (°C), Fahrenheit (°F), and Kelvin (K). - **PI** Math.PI constant (≈3.14159). +### 📊 Interpoolation and regression + +- **interpolation(input: { function: (number) => number, value: number, points: number[] })** + Interpolates a set of data points from a function and value through Newton Interpolation. + +```js +interpolation({ + function: Math.log, + value: 2, + points: [1, 4, 6] +}) + +➔ 0.5658443469009827 +``` + +- **regression(input: number[])** + Regresses a set of data points. + +```js +regression([ + [-2, -1], + [1, 2], + [4, 59], + [-1, 4], + [3, 24], + [-4, -53] +]); + +➔ { + linear: { + m: 10.97864768683274, + b: 4.00355871886121 + }, + polynomial: { + a: 6.689189189189188, + b: 11.060810810810809, + c: -0.34459459459459435 + } +} +``` + ### 📊 Population Statistics - **populationDensity(population, area)** diff --git a/src/area.ts b/src/area.ts index ec3d52d..5523bc6 100644 --- a/src/area.ts +++ b/src/area.ts @@ -5,46 +5,50 @@ import { PI, squared } from './numbers.js'; */ export const area = { /** - * @param base Size of the base of the rectangle - * @param height The height of the rectangle + * @param {number} base Size of the base of the rectangle + * @param {number} height The height of the rectangle * - * @returns base * height + * @returns {number} base * height */ rect: (base: number, height: number): number => { return Math.floor(base * height); }, + /** - * @param base Size of the base of the triangle - * @param height The height of the triangle + * @param {number} base Size of the base of the triangle + * @param {number} height The height of the triangle * - * @returns (base * height) / 2 + * @returns {number} (base * height) / 2 */ triangle: (base: number, height: number): number => { return Math.floor((base * height) / 2); }, + /** - * @param D larger diagonal - * @param d smaller diagonal + * @param {number} D larger diagonal + * @param {number} d smaller diagonal * - * @returns (D * d) / 2 + * @returns {number} (D * d) / 2 */ rhombus: (D: number, d: number): number => { return Math.floor((D * d) / 2); }, + /** - * @param B Larger base - * @param b Smaller base - * @param height Trapezoid height + * @param {number} B Larger base + * @param {number} b Smaller base + * @param {number} height Trapezoid height * - * @returns ((B + b) * height) / 2 + * @returns {number} ((B + b) * height) / 2 */ trapezoid: (B: number, b: number, height: number): number => { return Math.floor(((B + b) * height) / 2); }, + /** - * @param radius Circle radius + * @param {number} radius Circle radius * - * @returns π * (radius * radius) + * @returns {number} π * (radius * radius) */ circle: (radius: number): number => { return Math.floor(PI * squared(radius)); diff --git a/src/index.ts b/src/index.ts index 7a826ea..58a6ca7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,5 @@ export * from './temperatures.js'; export * from './numbers.js'; export * from './area.js'; export * from './population.js'; +export * from './interpolation.js'; +export * from './regression.js'; diff --git a/src/interpolation.ts b/src/interpolation.ts new file mode 100644 index 0000000..990caf3 --- /dev/null +++ b/src/interpolation.ts @@ -0,0 +1,49 @@ +export interface interpolationInput { + function: (input: number) => number; + value: number; + points: number[]; +} + +function getNewtonInterpolationB( + input: interpolationInput, + end: number, + start: number, +): number { + if (end == start) { + return input.function(input.points[start]!); + } + + return ( + (getNewtonInterpolationB(input, end, start + 1) - + getNewtonInterpolationB(input, end - 1, start)) / + (input.points[end]! - input.points[start]!) + ); +} + +/** + * @param {interpolationInput} input Interpolation input + * + * @returns {number} Interpolation result through Newton Interpolation + */ +export function interpolation(input: interpolationInput): number { + if (input.points.length < 1) { + throw new Error('Points array must not be empty.'); + } + + let multiplier, + output = input.function(input.points[0]!); + + for (let multiplierIndex, index = 1; index < input.points.length; index++) { + for ( + multiplier = 1, multiplierIndex = 0; + multiplierIndex < index; + multiplierIndex++ + ) { + multiplier *= input.value - input.points[multiplierIndex]!; + } + + output += multiplier * getNewtonInterpolationB(input, multiplierIndex, 0); + } + + return output; +} diff --git a/src/numbers.ts b/src/numbers.ts index 33522c5..29ab6a0 100644 --- a/src/numbers.ts +++ b/src/numbers.ts @@ -1,49 +1,58 @@ export const PI = Math.PI; /** - * @param number The value of the number - * @returns boolean - * If it is even + * @param {number} number The value of the number + * + * @returns {boolean} If it is even */ export function isEven(number: number): boolean { return number % 2 == 0; } + /** - * @param number The value of the number - * @returns boolean - * If it is odd + * @param {number} number The value of the number + * + * @returns {boolean} If it is odd */ export function isOdd(number: number): boolean { return !isEven(number); } + /** - * @param x First number - * @param y Second number - * @returns The difference value between X and Y + * @param {number} x First number + * @param {number} y Second number + * + * @returns {number} The difference value between X and Y */ export function difference(x: number, y: number): number { return Math.max(x - y); } + /** - * @param x Target value - * @param times Times that the value can be multiplied by itself - * @returns The squared value + * @param {number} x Target value + * @param {number} [times] Times that the value can be multiplied by itself + * + * @returns {number} The squared value */ export function squared(x: number, times?: number): number { return Math.pow(x, times ?? 2); } + /** - * @param C Adjacent Cathetus - * @param c Opposite Cathetus - * @returns Value of the Hypotenuse + * @param {number} C Adjacent Cathetus + * @param {number} c Opposite Cathetus + * + * @returns {number} Value of the Hypotenuse */ export function hypotenuse(C: number, c: number): number { var h = squared(C) + squared(c); return Math.sqrt(h); } + export function cathetus(H: number, C: number): number { if (H <= C) throw new Error('Cathetus cannot be greater or equal than Hypotenuse.'); + return Math.sqrt(squared(H) - squared(C)); } diff --git a/src/regression.ts b/src/regression.ts new file mode 100644 index 0000000..13be977 --- /dev/null +++ b/src/regression.ts @@ -0,0 +1,112 @@ +export interface LinearRegressionOutput { + m: number; + b: number; +} + +export interface PolynomialRegressionOutput { + a: number; + b: number; + c: number; +} + +export interface RegressionOutput { + linear: LinearRegressionOutput; + polynomial: PolynomialRegressionOutput; +} + +function computePolynomialRegressionMatrix(matrix: number[]) { + let pivot, eliminationPivot; + + for (let i = 0, j, k; i < 3; i++) { + const pivotStartIndex = i * 4; + pivot = matrix[pivotStartIndex + i]!; + + for (j = 0; j < 4; j++) { + matrix[pivotStartIndex + j]! /= pivot; + } + + for (j = 0; j < 3; j++) { + if (j == i) { + continue; + } + + const nonPivotStartIndex = j * 4; + eliminationPivot = matrix[nonPivotStartIndex + i]!; + + for (k = i; k < 4; k++) { + matrix[nonPivotStartIndex + k]! -= + eliminationPivot * matrix[pivotStartIndex + k]!; + } + } + } +} + +/** + * @param {number[][]} input Regression input, must be an array of [x, y] + * + * @returns {RegressionOutput} Regression result for both linear and polynomial regression + */ +export function regression(input: number[][]): RegressionOutput { + if (input.length === 0) { + throw new Error('Input array must not be empty.'); + } + + let x, + y, + m_divisor, + sumX = 0, + sumY = 0, + sumXY = 0, + sumXSquared = 0, + sumXCubed = 0, + sumXPower4 = 0, + sumXSquaredY = 0; + + for (let index = 0; index < input.length; index++) { + x = input[index]![0]!; + y = input[index]![1]!; + + sumX += x; + sumY += y; + sumXY += x * y; + sumXSquared += x * x; + sumXCubed += x * x * x; + sumXPower4 += x * x * x * x; + sumXSquaredY += x * x * y; + } + + const m = + (m_divisor = input.length * sumXSquared - sumX * sumX) === 0 + ? 0 + : (input.length * sumXY - sumX * sumY) / m_divisor; + const b = (sumY - m * sumX) / input.length; + + const polMatrix = [ + input.length, + sumX, + sumXSquared, + sumY, + sumX, + sumXSquared, + sumXCubed, + sumXY, + sumXSquared, + sumXCubed, + sumXPower4, + sumXSquaredY, + ]; + + computePolynomialRegressionMatrix(polMatrix); + + return { + linear: { + m, + b, + }, + polynomial: { + a: polMatrix[3]!, + b: polMatrix[7]!, + c: polMatrix[11]!, + }, + }; +} diff --git a/src/temperatures.ts b/src/temperatures.ts index e141457..b65ceb9 100644 --- a/src/temperatures.ts +++ b/src/temperatures.ts @@ -3,32 +3,41 @@ */ /** - * @param temperature The temperature you want to convert to Celsius - * @param unit The temperature unit you want to convert + * @param {number} temperature The temperature you want to convert to Celsius + * @param {TemperatureUnits} [unit] The temperature unit you want to convert + * + * @returns {number} */ function toCelsius(temperature: number, unit?: TemperatureUnits): number { if (unit === 'C') return temperature; if (unit === 'K') return Math.floor(temperature - 273.15); + return Math.max(((temperature - 32) * 5) / 9); } /** - * @param temperature The temperature you want to convert to Celsius - * @param unit The temperature unit you want to convert + * @param {number} temperature The temperature you want to convert to Celsius + * @param {TemperatureUnits} [unit] The temperature unit you want to convert + * + * @returns {number} */ function toFahrenheit(temperature: number, unit?: TemperatureUnits): number { if (unit === 'F') return temperature; if (unit === 'K') return Math.floor((temperature * 5) / 9 + 459.67); + return Math.max((temperature * 9) / 5 + 32); } /** - * @param temperature The temperature you want to convert to Celsius - * @param unit The temperature unit you want to convert + * @param {number} temperature The temperature you want to convert to Celsius + * @param {TemperatureUnits} [unit] The temperature unit you want to convert + * + * @returns {number} */ function toKelvin(temperature: number, unit?: TemperatureUnits): number { if (unit === 'K') return temperature; if (unit === 'F') return Math.floor(((temperature + 459.67) * 5) / 9); + return Math.max(temperature + 273.15); } From 25fa31f10b2ff0e79213eb26eeb01e1fc026d37b Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 5 Mar 2025 16:12:09 +0700 Subject: [PATCH 2/4] doc: better describe regression in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b6129a..bd85cb2 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ interpolation({ ``` - **regression(input: number[])** - Regresses a set of data points. + Computes the linear and polynomial regression from a set of data points. ```js regression([ @@ -136,7 +136,7 @@ regression([ b: 11.060810810810809, c: -0.34459459459459435 } -} +} ``` ### 📊 Population Statistics From 64f709ad7acaa160ced3677bcc26c23a2d894b50 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 5 Mar 2025 16:15:59 +0700 Subject: [PATCH 3/4] style: convert this to camelCase --- src/regression.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/regression.ts b/src/regression.ts index 13be977..ff4df3b 100644 --- a/src/regression.ts +++ b/src/regression.ts @@ -53,7 +53,7 @@ export function regression(input: number[][]): RegressionOutput { let x, y, - m_divisor, + mDivisor, sumX = 0, sumY = 0, sumXY = 0, @@ -76,9 +76,9 @@ export function regression(input: number[][]): RegressionOutput { } const m = - (m_divisor = input.length * sumXSquared - sumX * sumX) === 0 + (mDivisor = input.length * sumXSquared - sumX * sumX) === 0 ? 0 - : (input.length * sumXY - sumX * sumY) / m_divisor; + : (input.length * sumXY - sumX * sumY) / mDivisor; const b = (sumY - m * sumX) / input.length; const polMatrix = [ From 0c966c4fae880caf51acb14b99c0c39bcfb36324 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 5 Mar 2025 16:21:09 +0700 Subject: [PATCH 4/4] style: make interface names PascalCase --- src/interpolation.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interpolation.ts b/src/interpolation.ts index 990caf3..d9eff8c 100644 --- a/src/interpolation.ts +++ b/src/interpolation.ts @@ -1,11 +1,11 @@ -export interface interpolationInput { +export interface InterpolationInput { function: (input: number) => number; value: number; points: number[]; } function getNewtonInterpolationB( - input: interpolationInput, + input: InterpolationInput, end: number, start: number, ): number { @@ -21,11 +21,11 @@ function getNewtonInterpolationB( } /** - * @param {interpolationInput} input Interpolation input + * @param {InterpolationInput} input Interpolation input * * @returns {number} Interpolation result through Newton Interpolation */ -export function interpolation(input: interpolationInput): number { +export function interpolation(input: InterpolationInput): number { if (input.points.length < 1) { throw new Error('Points array must not be empty.'); }