From efba313424bc52c2dbaaa9d5c9395d8ab7d2d28c Mon Sep 17 00:00:00 2001 From: Tarun Date: Thu, 28 Aug 2025 15:39:29 +0530 Subject: [PATCH 1/3] Manhattan Distance between two matrices and minimum manhattan distance between list of points of d dimenstions --- .../__tests__/manhattanDistance.test.js | 23 +++++++ .../minimumManhattanDistance.test.js | 65 +++++++++++++++++++ .../manhattan_distance/manhattanDistance.js | 26 ++++++++ .../minimumManhattanDistance.js | 65 +++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js create mode 100644 src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js create mode 100644 src/algorithms/math/manhattan_distance/manhattanDistance.js create mode 100644 src/algorithms/math/manhattan_distance/minimumManhattanDistance.js diff --git a/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js b/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js new file mode 100644 index 0000000000..fced50f9b7 --- /dev/null +++ b/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js @@ -0,0 +1,23 @@ +import { manhattanDistance } from '../manhattanDistance'; + +describe('manhattanDistance', () => { + it('should calculate Manhattan distance between vectors', () => { + expect(manhattanDistance([[1]], [[2]])).toEqual(1); + expect(manhattanDistance([[2]], [[1]])).toEqual(1); + expect(manhattanDistance([[5, 8]], [[7, 3]])).toEqual(7); + expect(manhattanDistance([[5], [8]], [[7], [3]])).toEqual(7); + expect(manhattanDistance([[8, 2, 6]], [[3, 5, 7]])).toEqual(9); + expect(manhattanDistance([[8], [2], [6]], [[3], [5], [7]])).toEqual(9); + expect(manhattanDistance([[[8]], [[2]], [[6]]], [[[3]], [[5]], [[7]]])).toEqual(9); + }); + + it('should throw an error in case if two matrices are of different shapes', () => { + expect(() => manhattanDistance([[1]], [[[2]]])).toThrowError( + 'Matrices have different dimensions', + ); + + expect(() => manhattanDistance([[1]], [[2, 3]])).toThrowError( + 'Matrices have different shapes', + ); + }); +}); \ No newline at end of file diff --git a/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js b/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js new file mode 100644 index 0000000000..15c63789a1 --- /dev/null +++ b/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js @@ -0,0 +1,65 @@ +import minimumManhattanDistance from '../minimumManhattanDistance'; + +describe('minimumManhattanDistance', () => { + it('should return 0 for an empty array or an array with a single point', () => { + expect(minimumManhattanDistance([])).toBe(0); + expect(minimumManhattanDistance([[1, 2]])).toBe(0); + }); + + it('should find the minimum distance for 2D points', () => { + const points = [ + [1, 1], + [3, 3], + [1, 4], + ]; + // d([1,1], [3,3]) = |1-3|+|1-3| = 4 + // d([1,1], [1,4]) = |1-1|+|1-4| = 3 + // d([3,3], [1,4]) = |3-1|+|3-4| = 3 + expect(minimumManhattanDistance(points)).toBe(3); + }); + + it('should find the minimum distance for 3D points', () => { + const points = [ + [0, 0, 0], + [2, 2, 2], + [3, 3, 3], + [1, 5, 1], + ]; + // d([2,2,2], [3,3,3]) = |2-3|+|2-3|+|2-3| = 1+1+1 = 3 + expect(minimumManhattanDistance(points)).toBe(3); + }); + + it('should return 0 if there are identical points', () => { + const points = [ + [10, 20, 5], + [1, 1, 1], + [4, 8, 12], + [10, 20, 5], + ]; + expect(minimumManhattanDistance(points)).toBe(0); + }); + + it('should work correctly with negative coordinates', () => { + const points = [ + [-1, -1], + [2, 2], + [-3, 3], + [2, 3], + ]; + // d([2,2], [2,3]) = |2-2|+|2-3| = 1 + expect(minimumManhattanDistance(points)).toBe(1); + }); + + it('should handle a larger set of points', () => { + const points = [ + [0, 0], + [100, 100], + [10, 10], + [90, 90], + [49, 50], + [50, 50], + ]; + // d([49,50], [50,50]) = |49-50|+|50-50| = 1 + expect(minimumManhattanDistance(points)).toBe(1); + }); +}); \ No newline at end of file diff --git a/src/algorithms/math/manhattan_distance/manhattanDistance.js b/src/algorithms/math/manhattan_distance/manhattanDistance.js new file mode 100644 index 0000000000..ec28aca379 --- /dev/null +++ b/src/algorithms/math/manhattan_distance/manhattanDistance.js @@ -0,0 +1,26 @@ +/** + * @typedef {import('../matrix/Matrix.js').Matrix} Matrix + */ + +import * as mtrx from '../matrix/Matrix'; + +/** + * Calculates the manhattan distance between 2 matrices. + * + * @param {Matrix} a + * @param {Matrix} b + * @returns {number} + * @trows {Error} + */ +export const manhattanDistance = (a, b) => { + mtrx.validateSameShape(a, b); + + let distanceTotal = 0; + + mtrx.walk(a, (indices, aCellValue) => { + const bCellValue = mtrx.getCellAtIndex(b, indices); + distanceTotal += Math.abs(aCellValue - bCellValue); + }); + + return distanceTotal; +}; \ No newline at end of file diff --git a/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js b/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js new file mode 100644 index 0000000000..873d4ffe0a --- /dev/null +++ b/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js @@ -0,0 +1,65 @@ +/** + * Calculates the Manhattan distance between two points (arrays of numbers). + * @param {number[]} a - The first point. + * @param {number[]} b - The second point. + * @returns {number} The Manhattan distance. + */ +const manhattanDistance = (a, b) => { + let distance = 0; + for (let i = 0; i < a.length; i += 1) { + distance += Math.abs(a[i] - b[i]); + } + return distance; +}; + +/** + * Finds the minimum Manhattan distance between any two points in a set. + * This is an implementation of the algorithm for the closest pair problem in + * Manhattan distance, which has a time complexity of O(n * log(n) * 2^d), + * where n is the number of points and d is the number of dimensions. + * + * @param {number[][]} points - An array of points, where each point is an array of numbers. + * @returns {number} The minimum Manhattan distance found. + */ +const minimumManhattanDistance = (points) => { + if (!points || points.length < 2) { + return 0; + } + + const n = points.length; + const d = points[0].length; + let minDistance = Infinity; + + // Generate all 2^d sign patterns (orientations). + // We only need 2^(d-1) because d(p,q) is the same for a mask and its inverse. + const limit = 1 << (d - 1); + for (let mask = 0; mask < limit; mask += 1) { + // Compute projection values for the current orientation. + const projections = []; + for (let i = 0; i < n; i += 1) { + let projectedValue = 0; + for (let j = 0; j < d; j += 1) { + // Determine the sign for the current dimension based on the bitmask. + const sign = ((mask >> j) & 1) ? 1 : -1; + projectedValue += sign * points[i][j]; + } + projections.push({ value: projectedValue, index: i }); + } + + // Sort points based on their projected values. + projections.sort((a, b) => a.value - b.value); + + // Check consecutive pairs in the sorted list. + // The closest pair for this orientation will be adjacent after sorting. + for (let i = 0; i < n - 1; i += 1) { + const pIndex = projections[i].index; + const qIndex = projections[i + 1].index; + const distance = manhattanDistance(points[pIndex], points[qIndex]); + minDistance = Math.min(minDistance, distance); + } + } + + return minDistance; +}; + +export default minimumManhattanDistance; \ No newline at end of file From 29f5b30c900bde3198f07fd5e25b699fbc459f02 Mon Sep 17 00:00:00 2001 From: Tarun Date: Thu, 28 Aug 2025 15:42:31 +0530 Subject: [PATCH 2/3] Manhattan Distance between two matrices and minimum manhattan distance between list of points of d dimenstions --- .../math/manhattan_distance/README.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/algorithms/math/manhattan_distance/README.md diff --git a/src/algorithms/math/manhattan_distance/README.md b/src/algorithms/math/manhattan_distance/README.md new file mode 100644 index 0000000000..6166e6d076 --- /dev/null +++ b/src/algorithms/math/manhattan_distance/README.md @@ -0,0 +1,27 @@ +# Manhattan Distance + +In mathematics, the **Manhattan distance**, also known as **L1 distance**, **taxicab distance**, or **city block distance**, between two points in a grid-based space is the sum of the absolute differences of their Cartesian coordinates. It is the distance a car would drive in a city laid out in square blocks, where you can only travel along horizontal and vertical streets. + +The name relates to the grid layout of the streets on the island of Manhattan, which causes the shortest path a car could take between two points to be the length of the path along the grid lines. + +![Manhattan vs Euclidean Distance](https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Manhattan_distance.svg/400px-Manhattan_distance.svg.png) + +In the image above, the red, blue, and yellow lines have the same length (12 units) and are the Manhattan distances between the two black points. The green line is the Euclidean distance, which has a length of approximately 8.49 units. + +## Formula + +The Manhattan distance, `d`, between two points `p` and `q` with `n` dimensions (given by Cartesian coordinates) is the sum of the lengths of the projections of the line segment between the points onto the coordinate axes. + +![Manhattan distance formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/ead7631ca37af0070e989f8415b4cd6886229720) + +For example, the Manhattan distance between the two points `(p1, p2)` and `(q1, q2)` is `|p1 - q1| + |p2 - q2|`. + +## Closest Pair Problem + +Finding the closest pair of points in a set is a fundamental problem in computational geometry. While the brute-force approach of calculating the distance between every pair of points takes `O(n^2)` time, a more efficient algorithm exists for Manhattan distance. + +The provided `minimumManhattanDistance` implementation uses an algorithm that runs in `O(n * log(n) * 2^d)` time, where `n` is the number of points and `d` is the number of dimensions. It works by projecting all points onto `2^(d-1)` different orientations. For each orientation, it sorts the points and checks only the adjacent pairs, as the closest pair for that specific projection will be next to each other in the sorted list. This significantly reduces the number of comparisons needed. + +## References + +- [Manhattan Distance on Wikipedia](https://en.wikipedia.org/wiki/Manhattan_distance) \ No newline at end of file From 9acc71274fec2505b42042235329c07c01439e8b Mon Sep 17 00:00:00 2001 From: Tarun Date: Thu, 28 Aug 2025 15:54:35 +0530 Subject: [PATCH 3/3] linting and tests --- .../manhattan_distance/__tests__/manhattanDistance.test.js | 4 ++-- .../__tests__/minimumManhattanDistance.test.js | 2 +- src/algorithms/math/manhattan_distance/manhattanDistance.js | 6 ++++-- .../math/manhattan_distance/minimumManhattanDistance.js | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js b/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js index fced50f9b7..427a94f395 100644 --- a/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js +++ b/src/algorithms/math/manhattan_distance/__tests__/manhattanDistance.test.js @@ -1,4 +1,4 @@ -import { manhattanDistance } from '../manhattanDistance'; +import manhattanDistance from '../manhattanDistance'; describe('manhattanDistance', () => { it('should calculate Manhattan distance between vectors', () => { @@ -20,4 +20,4 @@ describe('manhattanDistance', () => { 'Matrices have different shapes', ); }); -}); \ No newline at end of file +}); diff --git a/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js b/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js index 15c63789a1..32f2b049c0 100644 --- a/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js +++ b/src/algorithms/math/manhattan_distance/__tests__/minimumManhattanDistance.test.js @@ -62,4 +62,4 @@ describe('minimumManhattanDistance', () => { // d([49,50], [50,50]) = |49-50|+|50-50| = 1 expect(minimumManhattanDistance(points)).toBe(1); }); -}); \ No newline at end of file +}); diff --git a/src/algorithms/math/manhattan_distance/manhattanDistance.js b/src/algorithms/math/manhattan_distance/manhattanDistance.js index ec28aca379..469776c315 100644 --- a/src/algorithms/math/manhattan_distance/manhattanDistance.js +++ b/src/algorithms/math/manhattan_distance/manhattanDistance.js @@ -12,7 +12,7 @@ import * as mtrx from '../matrix/Matrix'; * @returns {number} * @trows {Error} */ -export const manhattanDistance = (a, b) => { +const manhattanDistance = (a, b) => { mtrx.validateSameShape(a, b); let distanceTotal = 0; @@ -23,4 +23,6 @@ export const manhattanDistance = (a, b) => { }); return distanceTotal; -}; \ No newline at end of file +}; + +export default manhattanDistance; diff --git a/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js b/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js index 873d4ffe0a..b9e1fc9e8e 100644 --- a/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js +++ b/src/algorithms/math/manhattan_distance/minimumManhattanDistance.js @@ -62,4 +62,4 @@ const minimumManhattanDistance = (points) => { return minDistance; }; -export default minimumManhattanDistance; \ No newline at end of file +export default minimumManhattanDistance;