Add Euclidean Distance algorithm.

This commit is contained in:
Oleksii Trekhleb 2020-12-19 19:21:32 +01:00
parent 59666ac947
commit 610f16fe20
7 changed files with 97 additions and 26 deletions

View File

@ -78,6 +78,7 @@ a set of rules that precisely define a sequence of operations.
* `B` [Fast Powering](src/algorithms/math/fast-powering) * `B` [Fast Powering](src/algorithms/math/fast-powering)
* `B` [Horner's method](src/algorithms/math/horner-method) - polynomial evaluation * `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` [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` [Integer Partition](src/algorithms/math/integer-partition)
* `A` [Square Root](src/algorithms/math/square-root) - Newton's method * `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 * `A` [Liu Hui π Algorithm](src/algorithms/math/liu-hui) - approximate π calculations based on N-gons

View File

@ -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)

View File

@ -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',
);
});
});

View File

@ -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;

View File

@ -58,7 +58,7 @@ const validate2D = (m) => {
* @param {Matrix} b * @param {Matrix} b
* @trows {Error} * @trows {Error}
*/ */
const validateSameShape = (a, b) => { export const validateSameShape = (a, b) => {
validateType(a); validateType(a);
validateType(b); validateType(b);
@ -177,7 +177,7 @@ export const t = (m) => {
* @param {Matrix} m * @param {Matrix} m
* @param {function(indices: CellIndices, c: Cell)} visit * @param {function(indices: CellIndices, c: Cell)} visit
*/ */
const walk = (m, visit) => { export const walk = (m, visit) => {
/** /**
* Traverses the matrix recursively. * Traverses the matrix recursively.
* *
@ -208,7 +208,7 @@ const walk = (m, visit) => {
* @param {CellIndices} cellIndices - Array of cell indices * @param {CellIndices} cellIndices - Array of cell indices
* @return {Cell} * @return {Cell}
*/ */
const getCellAtIndex = (m, cellIndices) => { export const getCellAtIndex = (m, cellIndices) => {
// We start from the row at specific index. // We start from the row at specific index.
let cell = m[cellIndices[0]]; let cell = m[cellIndices[0]];
// Going deeper into the next dimensions but not to the last one to preserve // 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 {CellIndices} cellIndices - Array of cell indices
* @param {Cell} cellValue - New cell value * @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. // We start from the row at specific index.
let cell = m[cellIndices[0]]; let cell = m[cellIndices[0]];
// Going deeper into the next dimensions but not to the last one to preserve // Going deeper into the next dimensions but not to the last one to preserve

View File

@ -25,7 +25,7 @@ describe('kNN', () => {
const inconsistent = () => { const inconsistent = () => {
kNN([[1, 1]], [1], [1]); kNN([[1, 1]], [1], [1]);
}; };
expect(inconsistent).toThrowError('Inconsistent vector lengths'); expect(inconsistent).toThrowError('Matrices have different shapes');
}); });
it('should find the nearest neighbour', () => { it('should find the nearest neighbour', () => {

View File

@ -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. * 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) * @param {number} k - number of nearest neighbors which will be taken into account (preferably odd)
* @return {number} - the class of the point * @return {number} - the class of the point
*/ */
import euclideanDistance from '../../math/euclidean-distance/euclideanDistance';
export default function kNN( export default function kNN(
dataSet, dataSet,
labels, labels,
@ -42,7 +25,7 @@ export default function kNN(
const distances = []; const distances = [];
for (let i = 0; i < dataSet.length; i += 1) { for (let i = 0; i < dataSet.length; i += 1) {
distances.push({ distances.push({
dist: euclideanDistance(dataSet[i], toClassify), dist: euclideanDistance([dataSet[i]], [toClassify]),
label: labels[i], label: labels[i],
}); });
} }