diff --git a/DIRECTORY.md b/DIRECTORY.md index 185bae95..83faf590 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -71,6 +71,10 @@ * Tries * [Tries.Test](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/data_structures/tries/test/tries.test.ts) * [Tries](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/data_structures/tries/tries.ts) + * Vectors + * Test + * [Vector2.Test](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/data_structures/vectors/test/vector2.test.ts) + * [Vector2](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/data_structures/vectors/vector2.ts) ## Dynamic Programming * [Coin Change](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/dynamic_programming/coin_change.ts) @@ -156,6 +160,8 @@ ## Search * [Binary Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/binary_search.ts) + * [Exponential Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/exponential_search.ts) + * [Fibonacci Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/fibonacci_search.ts) * [Interpolation Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/interpolation_search.ts) * [Jump Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/jump_search.ts) * [Linear Search](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/search/linear_search.ts) diff --git a/data_structures/vectors/test/vector2.test.ts b/data_structures/vectors/test/vector2.test.ts new file mode 100644 index 00000000..fa146c3b --- /dev/null +++ b/data_structures/vectors/test/vector2.test.ts @@ -0,0 +1,150 @@ +import { Vector2 } from '../vector2' + +describe('Vector2', () => { + describe('#equalsExactly', () => { + it('should compare equality correctly', () => { + expect(new Vector2(1, 0).equalsExactly(new Vector2(1, 0))).toBe(true) + + expect(new Vector2(1.23, 4.56).equalsExactly(new Vector2(0, 0))).toBe( + false + ) + }) + }) + + describe('#equalsApproximately', () => { + it('should compare equality (approximately) correctly', () => { + expect( + new Vector2(1, 0).equalsApproximately( + new Vector2(1, 0.0000001), + 0.000001 + ) + ).toBe(true) + + expect( + new Vector2(1.23, 4.56).equalsApproximately( + new Vector2(1.24, 4.56), + 0.000001 + ) + ).toBe(false) + }) + }) + + describe('#add', () => { + it('should add two vectors correctly', () => { + expect( + new Vector2(1, 0) + .add(new Vector2(0, 1)) + .equalsApproximately(new Vector2(1, 1), 0.000001) + ).toBe(true) + + expect( + new Vector2(-3.3, -9) + .add(new Vector2(-2.2, 3)) + .equalsApproximately(new Vector2(-5.5, -6), 0.000001) + ).toBe(true) + }) + }) + + describe('#subtract', () => { + it('should subtract two vectors correctly', () => { + expect( + new Vector2(1, 0) + .subtract(new Vector2(0, 1)) + .equalsApproximately(new Vector2(1, -1), 0.000001) + ).toBe(true) + + expect( + new Vector2(234.5, 1.7) + .subtract(new Vector2(3.3, 2.7)) + .equalsApproximately(new Vector2(231.2, -1), 0.000001) + ).toBe(true) + }) + }) + + describe('#multiply', () => { + it('should multiply two vectors correctly', () => { + expect( + new Vector2(1, 0) + .multiply(5) + .equalsApproximately(new Vector2(5, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(3.41, -7.12) + .multiply(-3.1) + .equalsApproximately(new Vector2(-10.571, 22.072), 0.000001) + ).toBe(true) + }) + }) + + describe('#length', () => { + it('should calculate its length correctly', () => { + expect(new Vector2(1, 0).length()).toBe(1) + + expect(new Vector2(-1, 1).length()).toBe(Math.sqrt(2)) + }) + }) + + describe('#normalize', () => { + it('should normalize vectors correctly', () => { + expect( + new Vector2(1, 0) + .normalize() + .equalsApproximately(new Vector2(1, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(1, -1) + .normalize() + .equalsApproximately( + new Vector2(Math.sqrt(2) / 2, -Math.sqrt(2) / 2), + 0.000001 + ) + ).toBe(true) + }) + }) + + describe('#distance', () => { + it('should calculate the distance between two vectors correctly', () => { + expect(new Vector2(0, 0).distance(new Vector2(0, -1))).toBe(1) + + expect(new Vector2(1, 0).distance(new Vector2(0, 1))).toBe(Math.sqrt(2)) + }) + }) + + describe('#dotProduct', () => { + it('should calculate the dot product correctly', () => { + expect(new Vector2(1, 0).dotProduct(new Vector2(0, 1))).toBe(0) + + expect(new Vector2(1, 2).dotProduct(new Vector2(3, 4))).toBe(11) // 1 * 3 + 2 * 4 + }) + }) + + describe('#rotate', () => { + it('should rotate a vector correctly', () => { + expect( + new Vector2(0, -1) + .rotate(Math.PI / 2) + .equalsApproximately(new Vector2(1, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(1.23, -4.56) + .rotate(Math.PI) + .equalsApproximately(new Vector2(-1.23, 4.56), 0.000001) + ).toBe(true) + }) + }) + + describe('#angleBetween', () => { + it('should calculate the angle between two vectors correctly', () => { + expect(new Vector2(1, 0).angleBetween(new Vector2(0, 1))).toBe( + Math.PI / 2 + ) + + expect(new Vector2(1, 0).angleBetween(new Vector2(1, -1))).toBe( + -Math.PI / 4 + ) + }) + }) +}) diff --git a/data_structures/vectors/vector2.ts b/data_structures/vectors/vector2.ts new file mode 100644 index 00000000..e0b61f16 --- /dev/null +++ b/data_structures/vectors/vector2.ts @@ -0,0 +1,145 @@ +/** + * In mathematics and physics, a vector is an element of a vector space. + * + * The Vector2-class implements 2-dimensional vectors together with various vector-operations. + * @see https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics). + */ + +class Vector2 { + x: number + y: number + + constructor(x: number, y: number) { + this.x = x + this.y = y + } + + /** + * Check for exact vector equality. + * + * @param vector The vector to compare to. + * @returns Whether they are exactly equal or not. + */ + equalsExactly(vector: Vector2): boolean { + return this.x === vector.x && this.y === vector.y + } + + /** + * Check for approximate vector equality. + * + * @param vector The vector to compare to. + * @param epsilon The allowed discrepancy for the x-values and the y-values. + * @returns Whether they are approximately equal or not. + */ + equalsApproximately(vector: Vector2, epsilon: number): boolean { + return ( + Math.abs(this.x - vector.x) < epsilon && + Math.abs(this.y - vector.y) < epsilon + ) + } + + /** + * Vector length. + * + * @returns The length of the vector. + */ + length(): number { + return Math.sqrt(this.x * this.x + this.y * this.y) + } + + /** + * Normalization sets the vector to length 1 while maintaining its direction. + * + * @returns The normalized vector. + */ + normalize(): Vector2 { + const length: number = this.length() + if (length === 0) { + throw new Error('Cannot normalize vectors of length 0') + } + return new Vector2(this.x / length, this.y / length) + } + + /** + * Vector addition + * + * @param vector The vector to be added. + * @returns The sum-vector. + */ + add(vector: Vector2): Vector2 { + const x: number = this.x + vector.x + const y: number = this.y + vector.y + return new Vector2(x, y) + } + + /** + * Vector subtraction + * + * @param vector The vector to be subtracted. + * @returns The difference-vector. + */ + subtract(vector: Vector2): Vector2 { + const x: number = this.x - vector.x + const y: number = this.y - vector.y + return new Vector2(x, y) + } + + /** + * Vector scalar multiplication + * + * @param scalar The factor by which to multiply the vector. + * @returns The scaled vector. + */ + multiply(scalar: number): Vector2 { + const x: number = this.x * scalar + const y: number = this.y * scalar + return new Vector2(x, y) + } + + /** + * Distance between this vector and another vector. + * + * @param vector The vector to which to calculate the distance. + * @returns The distance. + */ + distance(vector: Vector2): number { + const difference: Vector2 = this.subtract(vector) + return difference.length() + } + + /** + * Vector dot product + * + * @param vector The vector used for the multiplication. + * @returns The resulting dot product. + */ + dotProduct(vector: Vector2): number { + return this.x * vector.x + this.y * vector.y + } + + /** + * Vector rotation (see https://en.wikipedia.org/wiki/Rotation_matrix) + * + * @param angleInRadians The angle in radians by which to rotate the vector. + * @returns The rotated vector. + */ + rotate(angleInRadians: number): Vector2 { + const ca: number = Math.cos(angleInRadians) + const sa: number = Math.sin(angleInRadians) + const x: number = ca * this.x - sa * this.y + const y: number = sa * this.x + ca * this.y + return new Vector2(x, y) + } + + /** + * Measure angle between two vectors + * + * @param vector The 2nd vector for the measurement. + * @returns The angle in radians. + */ + angleBetween(vector: Vector2): number { + return Math.atan2(vector.y, vector.x) - Math.atan2(this.y, this.x) + } +} + +export { Vector2 }