From 100159858f7079099dfafbe6a97cb125cb876492 Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Tue, 10 Aug 2021 14:15:40 +0300 Subject: [PATCH 1/7] feat: add base --- __test__/hash-set.spec.ts | 53 +++++++++++++++++++++ src/HashSet/hash-set.ts | 97 +++++++++++++++++++++++++++++++++++++++ src/HashSet/index.ts | 1 + src/index.ts | 1 + tsconfig.json | 8 +++- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 __test__/hash-set.spec.ts create mode 100644 src/HashSet/hash-set.ts create mode 100644 src/HashSet/index.ts diff --git a/__test__/hash-set.spec.ts b/__test__/hash-set.spec.ts new file mode 100644 index 0000000..4fdcbf1 --- /dev/null +++ b/__test__/hash-set.spec.ts @@ -0,0 +1,53 @@ +import { HashSet } from "../src"; + +describe("Option", () => { + it("`isDisjoint` works correctly", () => { + expect( + new HashSet([2, 5, 1, 3]).isDisjoint(new HashSet([20])), + ).toBeTruthy(); + + expect(new HashSet([2, 5, 1, 3]).isDisjoint(new HashSet([2]))).toBeFalsy(); + }); + + describe("union", () => { + it("works correctly without same values", () => { + const diff = new HashSet([2, 5, 1, 3]).union(new HashSet([20])); + expect(diff.toArray()).toEqual([2, 5, 1, 3, 20]); + }); + + it("works correctly with same values", () => { + const diff = new HashSet([2, 5, 1, 3]).union(new HashSet([2, 20])); + expect(diff.toArray()).toEqual([2, 5, 1, 3, 20]); + }); + }); + + describe("`difference` method", () => { + it("works correctly with no diff", () => { + const diff = new HashSet([2, 5, 1, 3]).difference(new HashSet([20])); + expect(diff.toArray()).toEqual([2, 5, 1, 3]); + }); + + it("works correctly with no diff", () => { + const diff = new HashSet([2, 5, 1, 3]).difference(new HashSet([2])); + expect(diff.toArray()).toEqual([5, 1, 3]); + }); + }); + + describe("symmetricDifference", () => { + it("works correctly with different values", () => { + const diff = new HashSet([1, 2, 3, 5]).symmetricDifference( + new HashSet([1, 2, 3, 4]), + ); + + expect(diff.toArray()).toEqual([5, 4]); + }); + + it("works correctly without different values", () => { + const diff = new HashSet([1, 2, 3]).symmetricDifference( + new HashSet([1, 2, 3]), + ); + + expect(diff.toArray()).toEqual([]); + }); + }); +}); diff --git a/src/HashSet/hash-set.ts b/src/HashSet/hash-set.ts new file mode 100644 index 0000000..0572d26 --- /dev/null +++ b/src/HashSet/hash-set.ts @@ -0,0 +1,97 @@ +import { None, Option, Some } from "../Option"; +import { Clone } from "../utils"; +import { Vector } from "../Vector"; + +/** + * @class ASet + * + * @template T + * + * + * @see JS Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + * @see Rust HashSet https://doc.rust-lang.org/std/collections/struct.HashSet.html + * + * @category ASet + */ +export class HashSet extends Set implements Clone> { + public static get [Symbol.species]() { + return Set; + } + + public constructor(values?: Iterable) { + super(values); + } + + public clone(): HashSet { + return new HashSet(this); + } + + public toArray(): T[] { + return [...this]; + } + + public toVec(): Vector { + return Vector.fromArray(this.toArray()); + } + + /** + * Returns `true` if the set is a superset of another, i.e., `this` contains at least all the values in `other`. + */ + public isSuperset(other: HashSet): boolean { + return other.isSubset(this); + } + + /** + * Returns `true` if the set is a subset of another, i.e., `other` contains at least all the values in `this`. + */ + public isSubset(other: HashSet): boolean { + if (this.size <= other.size) { + return this.toArray().every((value) => other.has(value)); + } + + return false; + } + + /** + * Returns `true` if `this` has no elements in common with `other`. This is equivalent to checking for an empty intersection. + */ + public isDisjoint(other: HashSet): boolean { + return !this.toArray().some((value) => other.has(value)); + } + + /** + * Visits the values representing the difference, i.e., the values that are in `this` but not in `other`. + */ + public difference(other: HashSet): HashSet { + let diff = this.clone(); + + other.forEach((value) => diff.delete(value)); + + return diff; + } + + public symmetricDifference(other: HashSet): HashSet { + const diffSelf = this.difference(other); + const diffOther = other.difference(this); + + return diffSelf.union(diffOther); + } + + /** + * Visits the values representing the intersection, i.e., the values that are both in `this` and `other`. + */ + public intersection(other: HashSet): HashSet { + let inters = new HashSet(); + + inters.forEach((value) => other.has(value) && inters.add(value)); + + return inters; + } + + /** + * Visits the values representing the union, i.e., all the values in `this` or `other`, without duplicates. + */ + public union(other: HashSet): HashSet { + return new HashSet(this.toArray().concat(other.toArray())); + } +} diff --git a/src/HashSet/index.ts b/src/HashSet/index.ts new file mode 100644 index 0000000..e81aa1a --- /dev/null +++ b/src/HashSet/index.ts @@ -0,0 +1 @@ +export * from "./hash-set"; diff --git a/src/index.ts b/src/index.ts index 3fbe95d..8f5b797 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export * from "./HashSet"; export { Vector } from "./Vector"; export { Err, Ok, Result } from "./Result"; export { Some, None, Option } from "./Option"; diff --git a/tsconfig.json b/tsconfig.json index 0f5dbe1..a61bda2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,11 @@ "noImplicitAny": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.(spec|test).ts"] + "exclude": [ + "node_modules", + "typings/browser.d.ts", + "typings/browser", + "node_modules", + "**/*.(spec|test).ts" + ] } From 5426c75efb3727432f2fffd01d83f372fe0a9208 Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Thu, 12 Aug 2021 18:13:11 +0300 Subject: [PATCH 2/7] wip --- src/HashSet/hash-set.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/HashSet/hash-set.ts b/src/HashSet/hash-set.ts index 0572d26..ba195bc 100644 --- a/src/HashSet/hash-set.ts +++ b/src/HashSet/hash-set.ts @@ -94,4 +94,25 @@ export class HashSet extends Set implements Clone> { public union(other: HashSet): HashSet { return new HashSet(this.toArray().concat(other.toArray())); } + + /** + * Clears the set, returning all elements in an `Iterable`. + */ + public drain(): Array { + const elements = [...this]; + + this.clear(); + + return elements; + } + + public drainFilter boolean>( + fn: F, + ): Array { + return this.drain().filter(fn); + } + + public isEmpty(): boolean { + return this.size === 0; + } } From 8c36f07d6c73fced5695385e2c321412d387c475 Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Fri, 13 Aug 2021 11:42:37 +0300 Subject: [PATCH 3/7] docs: add more in-code docs --- src/HashSet/hash-set.ts | 69 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/HashSet/hash-set.ts b/src/HashSet/hash-set.ts index ba195bc..e318115 100644 --- a/src/HashSet/hash-set.ts +++ b/src/HashSet/hash-set.ts @@ -36,6 +36,23 @@ export class HashSet extends Set implements Clone> { /** * Returns `true` if the set is a superset of another, i.e., `this` contains at least all the values in `other`. + * + * ### Example + * ```ts + * const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset(new HashSet([15])); + * expect(isSuperset).toBeFalsy(); + * + * const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset(new HashSet([2, 3])); + * expect(isSuperset).toBeTruthy(); + * + * const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset(new HashSet([])); + * expect(isSuperset).toBeTruthy(); + * ``` + * + * ### Q/A + * - Is the empty set a superset of itself? + * **Yes. A ⊆ B ⟺ B ⊇ A so .... ∅ ⊆ ∅ ⟺ ∅ ⊇ ∅** + * @see [More about it](https://math.stackexchange.com/questions/334666/is-the-empty-set-a-subset-of-itself) */ public isSuperset(other: HashSet): boolean { return other.isSubset(this); @@ -54,6 +71,13 @@ export class HashSet extends Set implements Clone> { /** * Returns `true` if `this` has no elements in common with `other`. This is equivalent to checking for an empty intersection. + * + * ### Example + * + * ```ts + * expect(new HashSet([2, 5, 1, 3]).isDisjoint(new HashSet([20]))).toBeTruthy(); + * expect(new HashSet([2, 5, 1, 3]).isDisjoint(new HashSet([2]))).toBeFalsy(); + * ``` */ public isDisjoint(other: HashSet): boolean { return !this.toArray().some((value) => other.has(value)); @@ -61,6 +85,16 @@ export class HashSet extends Set implements Clone> { /** * Visits the values representing the difference, i.e., the values that are in `this` but not in `other`. + * + * ### Example + * + * ```ts + * const diff = new HashSet([2, 5, 1, 3]).difference(new HashSet([2])); + * expect(diff.toArray()).toEqual([5, 1, 3]); + * + * const diff = new HashSet([2, 5, 1, 3]).difference(new HashSet([20])); + * expect(diff.toArray()).toEqual([2, 5, 1, 3]); + * ``` */ public difference(other: HashSet): HashSet { let diff = this.clone(); @@ -70,6 +104,19 @@ export class HashSet extends Set implements Clone> { return diff; } + /** + * Visits the values representing the symmetric difference, i.e., the values that are in `this` or in `other` but not in both. + * + * ### Example + * + * ```ts + * const diff = new HashSet([1, 2, 3]).symmetricDifference(new HashSet([1, 2, 3])); + * expect(diff.toArray()).toEqual([]); + * + * const diff = new HashSet([1, 2, 3, 5]).symmetricDifference(new HashSet([1, 2, 3, 4])); + * expect(diff.toArray()).toEqual([5, 4]); + * ``` + */ public symmetricDifference(other: HashSet): HashSet { const diffSelf = this.difference(other); const diffOther = other.difference(this); @@ -79,17 +126,37 @@ export class HashSet extends Set implements Clone> { /** * Visits the values representing the intersection, i.e., the values that are both in `this` and `other`. + * + * ### Example + * + * ```ts + * const intersection = new HashSet([1, 2, 3]).intersection(new HashSet([4, 5])); + * expect(intersection.toArray()).toEqual([]); + * + * const intersection = new HashSet([1, 2, 3]).intersection(new HashSet([4, 2, 3, 4])); + * expect(intersection.toArray()).toEqual([2, 3]); + * ``` */ public intersection(other: HashSet): HashSet { let inters = new HashSet(); - inters.forEach((value) => other.has(value) && inters.add(value)); + other.forEach((value) => this.has(value) && inters.add(value)); return inters; } /** * Visits the values representing the union, i.e., all the values in `this` or `other`, without duplicates. + * + * ### Example + * + * ```ts + * const diff = new HashSet([2, 5, 1, 3]).union(new HashSet([20])); + * expect(diff.toArray()).toEqual([2, 5, 1, 3, 20]); + * + * const diff = new HashSet([2, 5, 1, 3]).union(new HashSet([2, 20])); + * expect(diff.toArray()).toEqual([2, 5, 1, 3, 20]); + * ``` */ public union(other: HashSet): HashSet { return new HashSet(this.toArray().concat(other.toArray())); From 79f5d56cfe21325d1a92c7ed32d505799909ad28 Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Fri, 13 Aug 2021 11:42:52 +0300 Subject: [PATCH 4/7] test: add more test for `HashSet` --- __test__/hash-set.spec.ts | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/__test__/hash-set.spec.ts b/__test__/hash-set.spec.ts index 4fdcbf1..19bbb28 100644 --- a/__test__/hash-set.spec.ts +++ b/__test__/hash-set.spec.ts @@ -9,6 +9,36 @@ describe("Option", () => { expect(new HashSet([2, 5, 1, 3]).isDisjoint(new HashSet([2]))).toBeFalsy(); }); + describe("isSuperset", () => { + it("works correct when set a empty", () => { + const isSuperset = new HashSet([]).isSuperset(new HashSet([])); + + expect(isSuperset).toBeTruthy(); + }); + + it("works correct with empty set", () => { + const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset(new HashSet([])); + + expect(isSuperset).toBeTruthy(); + }); + + it("works correct when is superset", () => { + const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset( + new HashSet([2, 3]), + ); + + expect(isSuperset).toBeTruthy(); + }); + + it("works correct when is NOT superset", () => { + const isSuperset = new HashSet([1, 2, 3, 4]).isSuperset( + new HashSet([15]), + ); + + expect(isSuperset).toBeFalsy(); + }); + }); + describe("union", () => { it("works correctly without same values", () => { const diff = new HashSet([2, 5, 1, 3]).union(new HashSet([20])); @@ -21,6 +51,22 @@ describe("Option", () => { }); }); + describe("intersection", () => { + it("works correctly without same values", () => { + const intersection = new HashSet([1, 2, 3]).intersection( + new HashSet([4, 5]), + ); + expect(intersection.toArray()).toEqual([]); + }); + + it("works correctly with same values", () => { + const intersection = new HashSet([1, 2, 3]).intersection( + new HashSet([4, 2, 3, 4]), + ); + expect(intersection.toArray()).toEqual([2, 3]); + }); + }); + describe("`difference` method", () => { it("works correctly with no diff", () => { const diff = new HashSet([2, 5, 1, 3]).difference(new HashSet([20])); From fae8bbf7f87676ab864632d5c19e5d54e5c0ddde Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Fri, 13 Aug 2021 11:51:00 +0300 Subject: [PATCH 5/7] docs: update docs --- src/HashSet/hash-set.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/HashSet/hash-set.ts b/src/HashSet/hash-set.ts index e318115..3dab206 100644 --- a/src/HashSet/hash-set.ts +++ b/src/HashSet/hash-set.ts @@ -3,15 +3,25 @@ import { Clone } from "../utils"; import { Vector } from "../Vector"; /** - * @class ASet + * @class HashSet * * @template T * + * ### Definition ([source](https://en.wikipedia.org/wiki/Set_(abstract_data_type))): + * A **set** is an abstract data type that can store unique values, without any particular order. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests a value for membership in a set. * - * @see JS Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set - * @see Rust HashSet https://doc.rust-lang.org/std/collections/struct.HashSet.html * - * @category ASet + * #### Use the Set variant of any of these Maps when: ([source](https://doc.rust-lang.org/std/collections/index.html#use-the-set-variant-of-any-of-these-maps-when)) + * - You just want to remember which keys you’ve seen. + * - There is no meaningful value to associate with your keys. + * - You just want a set. + * + * #### References + * @see [JS Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) + * @see [Rust HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html) + * @see [The Empty Set](https://www.math.drexel.edu/~tolya/emptyset.pdf) + * + * @category HashSet */ export class HashSet extends Set implements Clone> { public static get [Symbol.species]() { From 05fe9312f66bc698d500b1ab78614da3a120a232 Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Wed, 25 Aug 2021 12:56:52 +0300 Subject: [PATCH 6/7] wip --- src/HashSet/hash-set.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/HashSet/hash-set.ts b/src/HashSet/hash-set.ts index 3dab206..90e92c7 100644 --- a/src/HashSet/hash-set.ts +++ b/src/HashSet/hash-set.ts @@ -174,6 +174,14 @@ export class HashSet extends Set implements Clone> { /** * Clears the set, returning all elements in an `Iterable`. + * + * ### Example + * ```ts + * const set = new HashSet([1, 2, 3, 4]); + * + * expect(set.drain()).toEqual([1, 2, 3, 4]); + * expect(set.isEmpty()).toBeTruthy(); + * ``` */ public drain(): Array { const elements = [...this]; @@ -183,12 +191,45 @@ export class HashSet extends Set implements Clone> { return elements; } + /** + * + * ### Example + * ```ts + * const set = new HashSet([1, 2, 3, 4]); + * + * expect(set.drainFilter((v) => v % 2 === 0)).toEqual([2, 4]); + * expect(set.isEmpty()).toBeTruthy(); + * ``` + */ public drainFilter boolean>( fn: F, ): Array { return this.drain().filter(fn); } + /** + * Retains only the elements specified by the predicate. + * + * In other words, remove all elements `e` such that `fn(e)` returns `false`. + * + * ### Example + * ```ts + * const set = new HashSet([1, 2, 3, 4]); + * + * set.retain((v) => v % 2 === 0); + * + * expect(set.toArray()).toEqual([2, 4]); + * expect(set.isEmpty()).toBeFalsy(); + * ``` + */ + public retain boolean>(fn: F): void { + for (let element of this) { + if (!fn(element)) { + this.delete(element); + } + } + } + public isEmpty(): boolean { return this.size === 0; } From c38dbb29ca05acc03d94c1b415308d87b95fc37f Mon Sep 17 00:00:00 2001 From: faramozzayw Date: Wed, 25 Aug 2021 12:57:10 +0300 Subject: [PATCH 7/7] test: add some tests --- __test__/hash-set.spec.ts | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/__test__/hash-set.spec.ts b/__test__/hash-set.spec.ts index 19bbb28..6d9b97f 100644 --- a/__test__/hash-set.spec.ts +++ b/__test__/hash-set.spec.ts @@ -96,4 +96,56 @@ describe("Option", () => { expect(diff.toArray()).toEqual([]); }); }); + + describe("drain", () => { + it("works correctly with empty set", () => { + const emptySet = new HashSet(); + + expect(emptySet.drain()).toEqual([]); + expect(emptySet.isEmpty()).toBeTruthy(); + }); + + it("works correctly with a set that has values", () => { + const set = new HashSet([1, 2, 3, 4]); + + expect(set.drain()).toEqual([1, 2, 3, 4]); + expect(set.isEmpty()).toBeTruthy(); + }); + }); + + describe("drainFilter", () => { + it("works correctly with empty set", () => { + const emptySet = new HashSet(); + + expect(emptySet.drainFilter((v) => v !== null)).toEqual([]); + expect(emptySet.isEmpty()).toBeTruthy(); + }); + + it("works correctly with a set that has values", () => { + const set = new HashSet([1, 2, 3, 4]); + + expect(set.drainFilter((v) => v % 2 === 0)).toEqual([2, 4]); + expect(set.isEmpty()).toBeTruthy(); + }); + }); + + describe("retain", () => { + it("works correctly with empty set", () => { + const emptySet = new HashSet(); + + emptySet.retain((v) => v !== null); + + expect(emptySet.toArray()).toEqual([]); + expect(emptySet.isEmpty()).toBeTruthy(); + }); + + it("works correctly with a set that has values", () => { + const set = new HashSet([1, 2, 3, 4]); + + set.retain((v) => v % 2 === 0); + + expect(set.toArray()).toEqual([2, 4]); + expect(set.isEmpty()).toBeFalsy(); + }); + }); });