From adda5c53b37c221573480ec6b949f0f424e4034f Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Sat, 19 Dec 2020 19:26:02 +0100 Subject: [PATCH] Add Euclidean Distance formula (#602) * Add Matrices section with basic Matrix operations (multiplication, transposition, etc.) * Add Euclidean Distance algorithm. --- README.md | 1 + .../math/euclidean-distance/README.md | 36 +++++++++++++++++++ .../__tests__/euclideanDistance.test.js | 23 ++++++++++++ .../euclidean-distance/euclideanDistance.js | 28 +++++++++++++++ src/algorithms/math/matrix/Matrix.js | 8 ++--- src/algorithms/ml/knn/__test__/knn.test.js | 2 +- src/algorithms/ml/knn/kNN.js | 25 +++---------- 7 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 src/algorithms/math/euclidean-distance/README.md create mode 100644 src/algorithms/math/euclidean-distance/__tests__/euclideanDistance.test.js create mode 100644 src/algorithms/math/euclidean-distance/euclideanDistance.js diff --git a/README.md b/README.md index 454d70ff..8f52db1d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ a set of rules that precisely define a sequence of operations. * `B` [Fast Powering](src/algorithms/math/fast-powering) * `B` [Horner's method](src/algorithms/math/horner-method) - polynomial evaluation * `B` [Matrices](src/algorithms/math/matrix) - matrices and basic matrix operations (multiplication, transposition, etc.) + * `B` [Euclidean Distance](src/algorithms/math/euclidean-distance) - distance between two points/vectors/matrices * `A` [Integer Partition](src/algorithms/math/integer-partition) * `A` [Square Root](src/algorithms/math/square-root) - Newton's method * `A` [Liu Hui π Algorithm](src/algorithms/math/liu-hui) - approximate π calculations based on N-gons diff --git a/src/algorithms/math/euclidean-distance/README.md b/src/algorithms/math/euclidean-distance/README.md new file mode 100644 index 00000000..d67c217e --- /dev/null +++ b/src/algorithms/math/euclidean-distance/README.md @@ -0,0 +1,36 @@ +# Euclidean Distance + +In mathematics, the **Euclidean distance** between two points in Euclidean space is the length of a line segment between the two points. It can be calculated from the Cartesian coordinates of the points using the Pythagorean theorem, therefore occasionally being called the Pythagorean distance. + +![Euclidean distance between two points](https://upload.wikimedia.org/wikipedia/commons/5/55/Euclidean_distance_2d.svg) + +## Distance formulas + +### One dimension + +The distance between any two points on the real line is the absolute value of the numerical difference of their coordinates + +![One dimension formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/7d75418dbec9482dbcb70f9063ad66e9cf7b5db9) + +### Two dimensions + +![Two dimensions formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/9c0157084fd89f5f3d462efeedc47d3d7aa0b773) + +### Higher dimensions + +In three dimensions, for points given by their Cartesian coordinates, the distance is + +![Three dimensions formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/d1d13a40a7b203b455ae6d4be8b3cce898bda625) + +Example: the distance between the two points `(8,2,6)` and `(3,5,7)`: + +![3-dimension example](https://www.mathsisfun.com/algebra/images/dist-2-points-3d.svg) + +In general, for points given by Cartesian coordinates in `n`-dimensional Euclidean space, the distance is + +![n-dimensional formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/a0ef4fe055b2a51b4cca43a05e5d1cd93f758dcc) + +## References + +- [Euclidean Distance on MathIsFun](https://www.mathsisfun.com/algebra/distance-2-points.html) +- [Euclidean Distance on Wikipedia](https://en.wikipedia.org/wiki/Euclidean_distance) diff --git a/src/algorithms/math/euclidean-distance/__tests__/euclideanDistance.test.js b/src/algorithms/math/euclidean-distance/__tests__/euclideanDistance.test.js new file mode 100644 index 00000000..78d7d8dc --- /dev/null +++ b/src/algorithms/math/euclidean-distance/__tests__/euclideanDistance.test.js @@ -0,0 +1,23 @@ +import euclideanDistance from '../euclideanDistance'; + +describe('euclideanDistance', () => { + it('should calculate euclidean distance between vectors', () => { + expect(euclideanDistance([[1]], [[2]])).toEqual(1); + expect(euclideanDistance([[2]], [[1]])).toEqual(1); + expect(euclideanDistance([[5, 8]], [[7, 3]])).toEqual(5.39); + expect(euclideanDistance([[5], [8]], [[7], [3]])).toEqual(5.39); + expect(euclideanDistance([[8, 2, 6]], [[3, 5, 7]])).toEqual(5.92); + expect(euclideanDistance([[8], [2], [6]], [[3], [5], [7]])).toEqual(5.92); + expect(euclideanDistance([[[8]], [[2]], [[6]]], [[[3]], [[5]], [[7]]])).toEqual(5.92); + }); + + it('should throw an error in case if two matrices are of different shapes', () => { + expect(() => euclideanDistance([[1]], [[[2]]])).toThrowError( + 'Matrices have different dimensions', + ); + + expect(() => euclideanDistance([[1]], [[2, 3]])).toThrowError( + 'Matrices have different shapes', + ); + }); +}); diff --git a/src/algorithms/math/euclidean-distance/euclideanDistance.js b/src/algorithms/math/euclidean-distance/euclideanDistance.js new file mode 100644 index 00000000..afa5c2fe --- /dev/null +++ b/src/algorithms/math/euclidean-distance/euclideanDistance.js @@ -0,0 +1,28 @@ +/** + * @typedef {import('../matrix/Matrix.js').Matrix} Matrix + */ + +import * as mtrx from '../matrix/Matrix'; + +/** + * Calculates the euclidean distance between 2 matrices. + * + * @param {Matrix} a + * @param {Matrix} b + * @returns {number} + * @trows {Error} + */ +const euclideanDistance = (a, b) => { + mtrx.validateSameShape(a, b); + + let squaresTotal = 0; + + mtrx.walk(a, (indices, aCellValue) => { + const bCellValue = mtrx.getCellAtIndex(b, indices); + squaresTotal += (aCellValue - bCellValue) ** 2; + }); + + return Number(Math.sqrt(squaresTotal).toFixed(2)); +}; + +export default euclideanDistance; diff --git a/src/algorithms/math/matrix/Matrix.js b/src/algorithms/math/matrix/Matrix.js index 2470eb39..2aee126b 100644 --- a/src/algorithms/math/matrix/Matrix.js +++ b/src/algorithms/math/matrix/Matrix.js @@ -58,7 +58,7 @@ const validate2D = (m) => { * @param {Matrix} b * @trows {Error} */ -const validateSameShape = (a, b) => { +export const validateSameShape = (a, b) => { validateType(a); validateType(b); @@ -177,7 +177,7 @@ export const t = (m) => { * @param {Matrix} m * @param {function(indices: CellIndices, c: Cell)} visit */ -const walk = (m, visit) => { +export const walk = (m, visit) => { /** * Traverses the matrix recursively. * @@ -208,7 +208,7 @@ const walk = (m, visit) => { * @param {CellIndices} cellIndices - Array of cell indices * @return {Cell} */ -const getCellAtIndex = (m, cellIndices) => { +export const getCellAtIndex = (m, cellIndices) => { // We start from the row at specific index. let cell = m[cellIndices[0]]; // Going deeper into the next dimensions but not to the last one to preserve @@ -227,7 +227,7 @@ const getCellAtIndex = (m, cellIndices) => { * @param {CellIndices} cellIndices - Array of cell indices * @param {Cell} cellValue - New cell value */ -const updateCellAtIndex = (m, cellIndices, cellValue) => { +export const updateCellAtIndex = (m, cellIndices, cellValue) => { // We start from the row at specific index. let cell = m[cellIndices[0]]; // Going deeper into the next dimensions but not to the last one to preserve diff --git a/src/algorithms/ml/knn/__test__/knn.test.js b/src/algorithms/ml/knn/__test__/knn.test.js index ccd69cd7..302bf3b7 100644 --- a/src/algorithms/ml/knn/__test__/knn.test.js +++ b/src/algorithms/ml/knn/__test__/knn.test.js @@ -25,7 +25,7 @@ describe('kNN', () => { const inconsistent = () => { kNN([[1, 1]], [1], [1]); }; - expect(inconsistent).toThrowError('Inconsistent vector lengths'); + expect(inconsistent).toThrowError('Matrices have different shapes'); }); it('should find the nearest neighbour', () => { diff --git a/src/algorithms/ml/knn/kNN.js b/src/algorithms/ml/knn/kNN.js index 24709af6..350e7ce1 100644 --- a/src/algorithms/ml/knn/kNN.js +++ b/src/algorithms/ml/knn/kNN.js @@ -1,23 +1,3 @@ -/** - * Calculates calculate the euclidean distance between 2 vectors. - * - * @param {number[]} x1 - * @param {number[]} x2 - * @returns {number} - */ -function euclideanDistance(x1, x2) { - // Checking for errors. - if (x1.length !== x2.length) { - throw new Error('Inconsistent vector lengths'); - } - // Calculate the euclidean distance between 2 vectors and return. - let squaresTotal = 0; - for (let i = 0; i < x1.length; i += 1) { - squaresTotal += (x1[i] - x2[i]) ** 2; - } - return Number(Math.sqrt(squaresTotal).toFixed(2)); -} - /** * Classifies the point in space based on k-nearest neighbors algorithm. * @@ -27,6 +7,9 @@ function euclideanDistance(x1, x2) { * @param {number} k - number of nearest neighbors which will be taken into account (preferably odd) * @return {number} - the class of the point */ + +import euclideanDistance from '../../math/euclidean-distance/euclideanDistance'; + export default function kNN( dataSet, labels, @@ -42,7 +25,7 @@ export default function kNN( const distances = []; for (let i = 0; i < dataSet.length; i += 1) { distances.push({ - dist: euclideanDistance(dataSet[i], toClassify), + dist: euclideanDistance([dataSet[i]], [toClassify]), label: labels[i], }); }