Skip to content

Instantly share code, notes, and snippets.

@Plutanium
Last active October 23, 2023 18:19
Show Gist options
  • Save Plutanium/2c70708d2c26b87295707571c6a52b62 to your computer and use it in GitHub Desktop.
Save Plutanium/2c70708d2c26b87295707571c6a52b62 to your computer and use it in GitHub Desktop.
Person
type PartialPick<T, F extends keyof T> = Omit<T, F> & Partial<Pick<T, F>>;
type PersonParams = {
firstName: string,
lastName: string,
birthDate?: Date
gender: Gender
mother?: Person | PersonParams
fatherOrMother?: Person | PersonParams
}
export class Gender {
readonly gender;
private constructor(gender: string) {
this.gender = gender;
}
static MALE = new this("male");
static FEMALE = new this("female");
}
Object.freeze(Gender.MALE);
Object.freeze(Gender.FEMALE);
export default class Person {
firstName: string;
lastName: string;
gender: Gender;
birthDate: Date | undefined;
#mother: Person | undefined;
#fatherOrMother: Person | undefined;
#children: Person[] = [];
constructor(p: PersonParams) {
this.firstName = p.firstName;
this.lastName = p.lastName;
this.birthDate = p.birthDate;
this.gender = p.gender;
this.mother = p.mother;
this.fatherOrMother = p.fatherOrMother;
this.birthDate = p.birthdate;
}
get age(): number {
if (!this.birthDate) {
throw new Error("Missing birthdate");
}
const now = new Date();
const yearDiff = now.getFullYear() - this.birthDate.getFullYear();
if (now.getMonth() > this.birthDate.getMonth()) {
return yearDiff;
}
if (now.getMonth() < this.birthDate.getMonth()) {
return yearDiff - 1;
}
if (now.getDate() >= this.birthDate.getDate()) {
return yearDiff;
}
return yearDiff - 1;
}
get aunts(): Person[] {
return this.getParentsSiblings(Gender.FEMALE);
}
set brothers(brothers: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#setSiblings(brothers, Gender.MALE);
}
get brothers(): Person[] {
return this.siblings.filter(sibling => sibling.gender === Gender.MALE);
}
set children(children: Person | PersonParams | Array<Person | PersonParams> | undefined) {
if (!children) {
this.#children = [];
return;
}
if (!Array.isArray(children)) {
children = [children];
}
children = (children as Array<Person | PersonParams>).filter(child => child !== this);
children = [... new Set(children)];
this.#children = [];
children.forEach(child => this.addChild(child));
}
get children(): Person[] {
return Array.from(this.#children);
}
get cousins(): Person[] {
const cousins = this.uncles.map(uncle => uncle.#children)
.concat(this.aunts.map(aunt => aunt.#children))
.flat()
.filter(cousin => cousin !== this);
return Array.from(new Set(cousins));
}
set daughters(daughters: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#children = this.sons;
if (!daughters) {
return;
}
if (!Array.isArray(daughters)) {
daughters = [daughters];
}
(daughters as Array<Person | PersonParams>)
.filter(child => child.gender === Gender.FEMALE)
.forEach(daughter => this.addChild(daughter));
}
get daughters(): Person[] {
return this.#children.filter(child => child.gender === Gender.FEMALE);
}
set fatherOrMother(fatherOrMother: Person | PersonParams | undefined) {
if (!fatherOrMother) {
this.#fatherOrMother = undefined;
return;
}
if (!(fatherOrMother instanceof Person)) {
fatherOrMother = new Person(fatherOrMother);
}
this.#updateParent(fatherOrMother as Person, fatherOrMother.gender);
this.#fatherOrMother = fatherOrMother as Person;
}
get fatherOrMother(): Person | undefined {
return this.#fatherOrMother;
}
set fullBrothers(fullBrothers: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#setFullSiblings(fullBrothers, Gender.MALE);
}
get fullBrothers(): Person[] {
return this.fullSiblings.filter(sibling => sibling.gender === Gender.MALE);
}
set fullSiblings(fullSiblings: Person | PersonParams | Array<Person | PersonParams> | undefined) {
if (!(this.#fatherOrMother && this.#mother)) {
throw new Error("Cannot set full siblings if both parents aren't present");
}
if (!fullSiblings) {
fullSiblings = [];
}
else if (!Array.isArray(fullSiblings)) {
fullSiblings = [fullSiblings];
}
const motherOnlyChildren = this.#mother.#children.filter(child => !this.#fatherOrMother!.#children.includes(child));
const fatherOnlyChildren = this.#fatherOrMother.#children.filter(child => !this.#mother!.#children.includes(child));
this.#mother.children = motherOnlyChildren;
this.#fatherOrMother.children = fatherOnlyChildren;
fullSiblings.forEach(sibling => this.addSibling(sibling));
}
get fullSiblings(): Person[] {
if (!(this.#fatherOrMother && this.#mother)) {
throw new Error("Cannot get full siblings if both parents aren't present");
}
return this.#mother.#children.filter(child => this.#fatherOrMother!.#children.includes(child) && child !== this);
}
set name(name: string) {
const [firstName, lastName] = name.split(" ");
if(!firstName || !lastName) {
throw new Error("name argument must be of the format {firstName} {lastName}");
}
this.firstName = firstName;
this.lastName = lastName;
}
get name(): string {
return this.firstName + " " + this.lastName
}
get nephews(): Person[] {
return this.getSiblingsChildren(Gender.MALE);
}
get nieces(): Person[] {
return this.getSiblingsChildren(Gender.FEMALE);
}
set mother(mother: Person | PersonParams | undefined) {
if (!mother) {
this.#mother = undefined;
return;
}
if (!(mother instanceof Person)) {
mother = new Person(mother);
}
this.#updateParent((mother as Person), Gender.FEMALE);
this.#mother = mother as Person;
}
get mother(): Person | undefined {
return this.#mother;
}
set fullSisters(fullSisters: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#setFullSiblings(fullSisters, Gender.FEMALE);
}
get fullSisters(): Person[] {
return this.fullSiblings.filter(sibling => sibling.gender === Gender.FEMALE);
}
get grandFathers(): Person[] {
return this.#getGrandParents(Gender.MALE);
}
get grandMothers(): Person[] {
return this.#getGrandParents(Gender.FEMALE);
}
get grandParents(): Person[] {
return this.#getGrandParents();
}
get halfBrothers(): Person[] {
return this.halfSiblings.filter(sibling => sibling.gender === Gender.MALE);
}
get halfSisters(): Person[] {
return this.halfSiblings.filter(sibling => sibling.gender === Gender.FEMALE);
}
get halfSiblings(): Person[] {
if (!(this.#fatherOrMother && this.#mother)) {
throw Error("Cannot get half siblings if both parents aren't present");
}
const fatherOnlyChildren = this.#fatherOrMother.#children.filter(child => !this.#mother!.#children.includes(child));
const motherOnlyChildren = this.#mother.#children.filter(child => !this.#fatherOrMother!.#children.includes(child));
return fatherOnlyChildren.concat(motherOnlyChildren);
}
set siblings(siblings: Person | PersonParams | Array<Person | PersonParams> | undefined) {
if (!this.#fatherOrMother && !this.#mother) {
throw new Error("Cannot set siblings if no parent is present");
}
if (this.#fatherOrMother) {
this.#fatherOrMother.children = [];
}
if (this.#mother) {
this.#mother.children = [];
}
if(!siblings) {
siblings = [];
}
else if (!Array.isArray(siblings)) {
siblings = [siblings];
}
siblings = (siblings as Array<Person | PersonParams>).filter(sibling => sibling !== this);
siblings = [...new Set(siblings), this];
if (this.#fatherOrMother) {
this.#fatherOrMother.children = siblings;
}
if (this.#mother) {
this.#mother.children = siblings;
}
}
get siblings(): Person[] {
if (!this.#mother && !this.#fatherOrMother) {
return [];
}
if (!this.#fatherOrMother && this.#mother || this.#fatherOrMother && !this.#mother) {
return (this.#mother || this.#fatherOrMother)!.#children
.filter(child => child !== this);
}
const motherChildren = this.#mother!.#children.filter(child => child !== this);
const fatherOrMotherChildren = this.#fatherOrMother!.#children.filter(child => child !== this && !motherChildren.includes(child));
return motherChildren.concat(fatherOrMotherChildren);
}
get siblingsChildren(): Person[] {
return this.getSiblingsChildren();
}
set sisters(sisters: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#setSiblings(sisters, Gender.FEMALE);
}
get sisters(): Person[] {
return this.siblings.filter(sibling => sibling.gender === Gender.FEMALE);
}
set sons(sons: Person | PersonParams | Array<Person | PersonParams> | undefined) {
this.#children = this.daughters;
if (!sons) {
return;
}
if (!Array.isArray(sons)) {
sons = [sons];
}
(sons as Array<Person | PersonParams>)
.filter(child => child.gender === Gender.MALE)
.forEach(son => this.addChild(son));
}
get sons(): Person[] {
return <Person[]>this.#children.filter(child => child.gender === Gender.MALE);
}
get uncles(): Person[] {
return this.getParentsSiblings(Gender.MALE);
}
addChild(child: Person | PersonParams): Person {
if (!(child instanceof Person)) {
child = new Person(child);
}
if(this.gender === Gender.MALE) {
child.fatherOrMother = this;
} else {
if(!child.mother) {
child.mother = this;
} else {
child.fatherOrMother = this;
}
}
return child as Person;
}
addSibling(sibling: Person | PersonParams): Person {
if (!(sibling instanceof Person)) {
sibling = new Person(sibling);
}
if (this === sibling) {
throw new Error("You cannot be your own sibling");
}
if (!this.#fatherOrMother && !this.#mother) {
throw new Error("Cannot add sibling if both parents are not present");
}
if (this.fatherOrMother) {
sibling.fatherOrMother = this.#fatherOrMother;
}
if (this.mother) {
sibling.mother = this.#mother;
}
return sibling as Person;
}
addBrother(brother: Person | PersonParams): Person {
return this.#addSibling(brother, Gender.MALE);
}
addSister(sister: Person | PersonParams): Person {
return this.#addSibling(sister, Gender.FEMALE);
}
isSibling(person: Person): boolean {
if(this === person) {
return false;
}
return this.siblings.includes(person);
}
getCousins(gender?: Gender): Person[] {
return gender
? this.cousins.filter(cousin => cousin.gender === gender)
: this.cousins;
}
getSiblingsChildren(gender?: Gender): Person[] {
let children = this.siblings
.map(sibling => sibling.#children)
.flat();
if (gender) {
children = children.filter(child => child.gender === gender);
}
return Array.from(new Set(children));
}
getParentsSiblings(gender?: Gender): Person[] {
let siblings = (this.#mother?.siblings || []).concat(this.#fatherOrMother?.siblings || []);
if (gender) {
siblings = siblings.filter(sibling => sibling.gender === gender);
}
return Array.from(new Set(siblings));
}
hasYuriParents() {
return this.mother && this.fatherOrMother?.gender === Gender.FEMALE;
}
makeChildWith(person: Person | PersonParams, child: Person|PartialPick<PersonParams,"lastName"|"gender">) {
if(this.gender === Gender.MALE && person.gender === Gender.MALE) {
throw new Error("Cannot make a child without a female parent");
}
if(!(person instanceof Person)) {
person = new Person(person);
}
if(this.gender === Gender.FEMALE && person.gender === Gender.FEMALE) {
child.gender = Gender.FEMALE;
} else if(!child.gender) {
child.gender = Math.random() > 0.5 ? Gender.FEMALE : Gender.MALE;
}
if(!child.lastName) {
child.lastName = Math.random() > 0.5 ? this.lastName : person.lastName;
}
if(!(child instanceof Person)) {
child = new Person(child as PersonParams);
}
this.addChild(child as Person);
(person as Person).addChild(child as Person);
return child as Person;
}
static makeMale(params: Omit<PersonParams,"gender">) {
return new Person({...params, gender: Gender.MALE});
}
static makeFemale(params: Omit<PersonParams,"gender">) {
return new Person({...params, gender: Gender.FEMALE});
}
#addSibling(sibling: Person | PersonParams, gender: Gender): Person {
if (!sibling) {
throw new TypeError("null or undefined value is not permitted");
}
if (!(sibling instanceof Person)) {
sibling = new Person(sibling);
}
sibling.gender = sibling.gender || gender;
if (sibling.gender !== gender) {
throw new Error("This sibling must be of the gender: " + gender);
}
return this.addSibling(sibling);
}
#getGrandParents(gender?: Gender): Person[] {
const grandParents: Array<Person|undefined> = [];
if (gender) {
const expectedParent = gender === Gender.MALE ? "fatherOrMother" : "mother";
if (this.#mother) {
grandParents.push(this.#mother[expectedParent]);
}
if (this.#fatherOrMother) {
grandParents.push(this.#fatherOrMother[expectedParent]);
}
} else {
if (this.#mother) {
grandParents.push(this.#mother.#fatherOrMother);
grandParents.push(this.#mother.#mother);
}
if (this.#fatherOrMother) {
grandParents.push(this.#fatherOrMother.#fatherOrMother);
grandParents.push(this.#fatherOrMother.#mother);
}
}
return Array.from(new Set(grandParents.filter(Boolean))) as Person[];
}
#setFullSiblings(fullSiblings: Person | PersonParams | Array<Person | PersonParams> | undefined, gender: Gender) {
if (!(this.#fatherOrMother && this.#mother)) {
throw new Error("Cannot set full siblings if both parents aren't present");
}
if (!fullSiblings) {
fullSiblings = [];
}
if (!Array.isArray(fullSiblings)) {
fullSiblings = [fullSiblings];
}
this.fullSiblings = gender === Gender.MALE ? this.fullBrothers : this.fullSisters;
fullSiblings.forEach(sibling => this.addSibling(sibling));
}
#setSiblings(siblings: Person | PersonParams | Array<Person | PersonParams> | undefined, gender: Gender) {
if (!siblings) {
siblings = [];
}
if (!Array.isArray(siblings)) {
siblings = [siblings];
}
this.siblings = gender === Gender.MALE ?
this.sisters :
this.brothers;
siblings.forEach(sibling => this.addSibling(sibling));
}
#updateParent(parent: Person, gender: Gender) {
if (this === parent) {
throw new Error("Parent cannot be self");
}
if (parent.gender !== gender) {
throw new Error("Mismatch between parent gender and gender provided");
}
if (!parent.#children.includes(this)) {
parent.#children.push(this);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment