Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Euclidean Distance formula #602

Merged
merged 3 commits into from
Dec 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions src/algorithms/math/euclidean-distance/README.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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',
);
});
});
28 changes: 28 additions & 0 deletions src/algorithms/math/euclidean-distance/euclideanDistance.js
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 4 additions & 4 deletions src/algorithms/math/matrix/Matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/ml/knn/__test__/knn.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
25 changes: 4 additions & 21 deletions src/algorithms/ml/knn/kNN.js
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -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,
Expand All @@ -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],
});
}
Expand Down