Skip to content

feat: add geometry triangle #1695

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

Closed
wants to merge 3 commits into from
Closed
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
95 changes: 95 additions & 0 deletions Geometry/Test/Triangle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Triangle from '../Triangle'

describe('Triangle', () => {
describe('Constructor', () => {
test('creates a triangle with base and height', () => {
const triangle = new Triangle(5, 10)
expect(triangle).toBeInstanceOf(Triangle)
expect(triangle.base).toBe(5)
expect(triangle.height).toBe(10)
expect(triangle.sides).toEqual([5, null, null])
})

test('creates a triangle with three sides', () => {
const triangle = new Triangle(3, 4, 5)
expect(triangle).toBeInstanceOf(Triangle)
expect(triangle.sides).toEqual([3, 4, 5])
expect(triangle.base).toBe(3) // Assuming base is one of the sides
expect(triangle.height).toBe(null)
})

test('throws an error if invalid number of arguments', () => {
expect(() => new Triangle()).toThrow(
'Invalid number of arguments. Use either (base, height) or (sideA, sideB, sideC).'
)
expect(() => new Triangle(1)).toThrow(
'Invalid number of arguments. Use either (base, height) or (sideA, sideB, sideC).'
)
})

test('throws an error if the triangle is invalid', () => {
expect(() => new Triangle(1, 2, 3)).toThrow(
'Invalid triangle: The sum of any two sides must be greater than the third side.'
)
})
})

describe('Area Calculation', () => {
test('calculates area correctly using base and height', () => {
const triangle = new Triangle(5, 10)
expect(triangle.area()).toBe(25)
})

test("calculates area correctly using Heron's formula", () => {
const triangle = new Triangle(3, 4, 5)
expect(triangle.area()).toBe(6) // Area of a 3-4-5 triangle
})
})

describe('Perimeter Calculation', () => {
test('calculates perimeter correctly for three sides', () => {
const triangle = new Triangle(3, 4, 5)
expect(triangle.perimeter()).toBe(12) // 3 + 4 + 5
})

test('throws an error if not all sides are known', () => {
const triangle = new Triangle(5, 10)
expect(() => triangle.perimeter()).toThrow(
'Cannot calculate perimeter: not all sides are known.'
)
})
})

describe('Getters', () => {
test('base getter returns correct value', () => {
const triangle = new Triangle(5, 10)
expect(triangle.base).toBe(5)
})

test('height getter returns correct value', () => {
const triangle = new Triangle(5, 10)
expect(triangle.height).toBe(10)
})

test('sides getter returns correct values', () => {
const triangle = new Triangle(3, 4, 5)
expect(triangle.sides).toEqual([3, 4, 5])
})
})

describe('String Representation', () => {
test('returns correct string representation for base and height', () => {
const triangle = new Triangle(5, 10)
expect(triangle.toString()).toBe(
'Triangle: base = 5, height = 10, sides = 5, unknown, unknown, area = 25, perimeter = unknown'
)
})

test('returns correct string representation for three sides', () => {
const triangle = new Triangle(3, 4, 5)
expect(triangle.toString()).toBe(
'Triangle: base = 3, height = unknown, sides = 3, 4, 5, area = 6, perimeter = 12'
)
})
})
})
157 changes: 157 additions & 0 deletions Geometry/Triangle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* This class represents a Triangle and provides methods to calculate its area and perimeter
* based on either all three sides or the base and height.
* @see {@link https://en.wikipedia.org/wiki/Triangle}
* @class
*/
export default class Triangle {
/** @private */
#base

/** @private */
#height

/** @private */
#sides

/**
* Creates a triangle instance.
* @constructor
* @param {...number} args - The dimensions of the triangle.
* @throws {Error} Will throw an error if the number of arguments is invalid or if the triangle is invalid.
*/
constructor(...args) {
if (args.length === 2) {
this.#initializeFromBaseAndHeight(...args)
} else if (args.length === 3) {
this.#initializeFromSides(...args)
} else {
throw new Error(
'Invalid number of arguments. Use either (base, height) or (sideA, sideB, sideC).'
)
}
}

/**
* Initializes the triangle from base and height.
* @private
* @param {number} base - The base of the triangle.
* @param {number} height - The height of the triangle.
*/
#initializeFromBaseAndHeight(base, height) {
this.#base = base
this.#height = height
this.#sides = [base, null, null]
}

/**
* Initializes the triangle from three sides.
* @private
* @param {number} sideA - The length of side A.
* @param {number} sideB - The length of side B.
* @param {number} sideC - The length of side C.
* @throws {Error} Will throw an error if the triangle is invalid.
*/
#initializeFromSides(sideA, sideB, sideC) {
if (!this.#isValidTriangle(sideA, sideB, sideC)) {
throw new Error(
'Invalid triangle: The sum of any two sides must be greater than the third side.'
)
}
this.#sides = [sideA, sideB, sideC]
this.#base = sideA // Assuming base is one of the sides for consistency
this.#height = null
}

/**
* Checks if three sides can form a valid triangle.
* @private
* @param {number} a - The length of side A.
* @param {number} b - The length of side B.
* @param {number} c - The length of side C.
* @returns {boolean} True if the triangle is valid; false otherwise.
*/
#isValidTriangle(a, b, c) {
return a + b > c && b + c > a && c + a > b
}

/**
* Calculates the area of the triangle.
* @public
* @returns {number} The area of the triangle.
*/
area() {
if (this.#height !== null) {
return 0.5 * this.#base * this.#height
}
// Using Heron's formula
const [a, b, c] = this.#sides
const s = (a + b + c) / 2
return Math.sqrt(s * (s - a) * (s - b) * (s - c))
}

/**
* Calculates the perimeter of the triangle.
* @public
* @returns {number} The perimeter of the triangle.
* @throws {Error} Will throw an error if not all sides are known.
*/
perimeter() {
if (this.#sides.some((side) => side === null)) {
throw new Error('Cannot calculate perimeter: not all sides are known.')
}
return this.#sides.reduce((sum, side) => sum + side, 0)
}

/**
* Returns a string representation of the triangle.
* @public
* @returns {string} A string describing the triangle's dimensions and area/perimeter.
*/
toString() {
const areaValue = this.area()
let perimeterValue

// Check if all sides are known for perimeter calculation
if (this.#sides.some((side) => side === null)) {
perimeterValue = 'unknown'
} else {
perimeterValue = this.perimeter()
}

return (
`Triangle: base = ${this.#base}, height = ${
this.#height ?? 'unknown'
}, ` +
`sides = ${this.#sides.map((side) => side ?? 'unknown').join(', ')}, ` +
`area = ${areaValue}, perimeter = ${perimeterValue}`
)
}

/**
* Gets the base of the triangle.
* @public
* @returns {number} The base of the triangle.
*/
get base() {
return this.#base
}

/**
* Gets the height of the triangle.
* @public
* @returns {number|null} The height of the triangle or null if not defined.
*/
get height() {
return this.#height
}

/**
* Gets the lengths of all sides of the triangle.
* @public
* @returns {Array<number|null>} An array containing lengths of all three sides or null for unknown sides.
*/
get sides() {
return this.#sides
}
}