Created
June 14, 2022 05:16
-
-
Save aarona/17e273f0e23235de1612959ad36d033d to your computer and use it in GitHub Desktop.
Object oriented example using Typscript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface Shape { | |
area(): number | |
perimeter(): number | |
} | |
export interface Describable { | |
describe(): string | |
} | |
type Coordinate = [number, number] | |
export abstract class DescribableShape implements Describable, Shape { | |
abstract area(): number | |
abstract perimeter(): number | |
abstract describe(): string | |
} | |
export class Rectangle implements DescribableShape { | |
constructor(readonly height: number, readonly length: number) { | |
if(height <= 0 || length <= 0) throw new Error('Invalid Rectangle') | |
} | |
area(): number { | |
return this.height * this.length | |
} | |
perimeter(): number { | |
return 2 * (this.height + this.length) | |
} | |
describe(): string { | |
return `A Rectangle of length ${this.length} and height ${this.height}` | |
} | |
} | |
export class Square extends Rectangle { | |
constructor(readonly side: number) { | |
super(side, side) | |
} | |
describe(): string { | |
return `A Square with sides of length ${this.side}` | |
} | |
} | |
export class Circle implements DescribableShape { | |
constructor(readonly radius: number) { | |
if (radius <= 0) throw new Error('Invalid Circle') | |
} | |
// Area = Pr^2 | |
area(): number { | |
return Math.PI * Math.pow(this.radius, 2) | |
} | |
// Perimeter = 2Pr | |
perimeter(): number { | |
return 2 * Math.PI * this.radius | |
} | |
describe(): string { | |
return `A Circle of radius ${this.radius}` | |
} | |
} | |
export class Triangle implements DescribableShape { | |
constructor(private sides: [number, number, number]) { | |
if(!this.validSides()) throw new Error('Invalid Triangle') | |
} | |
validSides(): boolean { | |
const { sides } = this | |
const side1Valid = sides[0] < sides[1] + sides[2] | |
const side2Valid = sides[1] < sides[0] + sides[2] | |
const side3Valid = sides[2] < sides[0] + sides[1] | |
return side1Valid && side2Valid && side3Valid | |
} | |
area(): number { | |
const { sides } = this | |
const p = this.perimeter() / 2 | |
const [p1, p2, p3] = [p - sides[0], p - sides[1], p - sides[2]] | |
return Math.sqrt(p * p1 * p2 * p3) | |
} | |
perimeter(): number { | |
return this.sides.reduce((result, currentValue) => result + currentValue, 0) | |
} | |
describe(): string { | |
const { sides } = this | |
return `A Triagle of side lengths ${sides[0]}, ${sides[1]} and ${sides[2]}` | |
} | |
} | |
export class RightAngleTriangle extends Triangle { | |
constructor(private base: number, private height: number) { | |
const hypotentuse = Math.sqrt(base**2 + height**2) | |
super([base, height, hypotentuse]) | |
} | |
area(): number { | |
return 0.5 * this.base * this.height | |
} | |
} | |
export class RegularPolygon implements DescribableShape { | |
constructor(readonly numberOfSides: number, readonly lengthOfSides: number) { | |
if (numberOfSides < 3 || lengthOfSides <= 0) throw new Error('Invalid Polygon') | |
} | |
area(): number { | |
return this.numberOfSides * this.lengthOfSides**2 * (1 / Math.tan(Math.PI / this.numberOfSides)) / 4 | |
} | |
perimeter(): number { | |
return this.lengthOfSides * this.numberOfSides | |
} | |
describe(): string { | |
const { lengthOfSides, numberOfSides } = this | |
const presetLabels = { | |
3: 'Triangle', | |
4: 'Square', | |
5: 'Pentagon', | |
6: 'Hexagon', | |
7: 'Heptagon', | |
8: 'Octogon', | |
9: 'Nonagon', | |
10: 'Decagon', | |
12: 'Dodecagon' | |
} | |
const key = numberOfSides as keyof typeof presetLabels | |
const label = | |
presetLabels[key] ? | |
`${presetLabels[key]} with` : | |
`Regular Polygon with ${numberOfSides}` | |
return `A ${label} equal sides of lenth ${lengthOfSides}` | |
} | |
} | |
export class Polygon implements DescribableShape { | |
constructor(private coordinates: Coordinate[]) { | |
if(coordinates.length < 3) throw new Error('invalid Polygon') | |
} | |
area(): number { | |
const { coordinates } = this | |
const numOfCoordinates = coordinates.length | |
let areaSum = 0 | |
for (let idx = 0; idx < numOfCoordinates; idx++) { | |
const nextIdx = idx + 1 === numOfCoordinates ? 0 : idx + 1 | |
const currentCoordinate = coordinates[idx] | |
const nextCoordinate = coordinates[nextIdx] | |
areaSum += this.calcAreaComponent(currentCoordinate, nextCoordinate) | |
} | |
return Math.abs(areaSum / 2) | |
} | |
perimeter(): number { | |
const { coordinates } = this | |
const numOfCoordinates = coordinates.length | |
let perimeter = 0 | |
for (let idx = 0; idx < numOfCoordinates; idx++) { | |
const nextIdx = idx + 1 === numOfCoordinates ? 0 : idx + 1 | |
const currentCoordinate = coordinates[idx] | |
const nextCoordinate = coordinates[nextIdx] | |
perimeter += this.distance(currentCoordinate, nextCoordinate) | |
} | |
return perimeter | |
} | |
describe(): string { | |
return `A Polygon with coordinates ${this.join()}` | |
} | |
private calcAreaComponent(a: Coordinate, b: Coordinate) { | |
const [x1, y1] = a | |
const [x2, y2] = b | |
return x1 * y2 - y1 * x2 | |
} | |
private distance(a: Coordinate, b: Coordinate) { | |
const [x1, y1] = a | |
const [x2, y2] = b | |
return Math.sqrt((x1 - x2)**2 + (y1 - y2)**2) | |
} | |
private join() { | |
const coordinates = this.coordinates.slice() | |
const [x, y] = coordinates.pop() as Coordinate | |
const coordinatesDescription = | |
this.coordinates.map(coordinate => | |
`[${coordinate[0]}, ${coordinate[1]}]`).join(', ') | |
return `${coordinatesDescription} and [${x}, ${y}]` | |
} | |
} | |
class ShapeCalculator { | |
constructor(private shapes: DescribableShape[]) {} | |
totalArea() { | |
return this.shapes.reduce((result, currentShape) => result + currentShape.area(), 0) | |
} | |
totalPerimeter() { | |
return this.shapes.reduce((result, currentShape) => result + currentShape.perimeter(), 0) | |
} | |
print() { | |
const s = this.shapes.map(shape => ({ | |
description: shape.describe(), | |
area: shape.area(), | |
perimeter: shape.perimeter() | |
})) | |
console.table(s) | |
} | |
} | |
const shapes = [ | |
new Circle(1), | |
new Square(2), | |
new RegularPolygon(100, 1), | |
new RegularPolygon(5, 2), | |
new Rectangle(2, 3), | |
new Triangle([1, 1, 1]), | |
new RightAngleTriangle(3, 4), | |
new Triangle([1.01, 99, 100]), | |
new RegularPolygon(11, 2), | |
new Polygon([[0, 0], [0, 1], [1, 1], [1, 0]]), | |
new Polygon([[0, 0], [0, 10], [1, 1], [10, 0]]) | |
] | |
const radius = 10 / (Math.PI * 2) | |
const circles = [ | |
new Circle(radius), | |
new RegularPolygon(10, 1), | |
new RegularPolygon(100, 0.1), | |
new RegularPolygon(1000, 0.01), | |
new RegularPolygon(100000, 0.0001), | |
] | |
const calc = new ShapeCalculator(shapes) | |
console.log('Total Area:', calc.totalArea()); | |
console.log('Total Perimeter:', calc.totalPerimeter()); | |
calc.print() | |
const calcCircles = new ShapeCalculator(circles) | |
calcCircles.print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment