|
1 |
| -/** |
2 |
| - * A high-performance Set implementation that maintains a hash value for fast equality comparison. |
3 |
| - * This allows for O(1) set comparison operations instead of O(n) when comparing two sets. |
4 |
| - * |
5 |
| - * @example |
6 |
| - * ```typescript |
7 |
| - * const set1 = new HashedSet([1, 2, 3]); |
8 |
| - * const set2 = new HashedSet([3, 2, 1]); |
9 |
| - * |
10 |
| - * // Fast comparison using hash values |
11 |
| - * console.log(set1.hash === set2.hash); // true - same elements, same hash |
12 |
| - * |
13 |
| - * // Standard Set operations work as expected |
14 |
| - * set1.add(4); |
15 |
| - * set1.delete(1); |
16 |
| - * console.log(set1.has(2)); // true |
17 |
| - * ``` |
18 |
| - */ |
19 |
| -export class HashedSet<T = any> extends Set<T> { |
20 |
| - private _hash: number; |
| 1 | +export class HashedSet extends Set<string> { |
| 2 | + private _hashHigh: number = 0; |
| 3 | + private _hashLow: number = 0; |
21 | 4 |
|
22 |
| - /** |
23 |
| - * Creates a new HashedSet instance. |
24 |
| - * @param iterable Optional iterable to initialize the set with |
25 |
| - */ |
26 |
| - constructor(iterable?: Iterable<T>) { |
27 |
| - super(iterable ? Array.from(iterable) : undefined); |
28 |
| - this._hash = 0; |
29 |
| - for (const v of this) { |
30 |
| - this._hash ^= this._valHash(v); |
| 5 | + constructor(iterable?: Iterable<string>) { |
| 6 | + super(iterable); |
| 7 | + for (const str of this) { |
| 8 | + const hash64 = this._djb2Dual(str); |
| 9 | + this._hashHigh ^= hash64.high; |
| 10 | + this._hashLow ^= hash64.low; |
31 | 11 | }
|
32 | 12 | }
|
33 | 13 |
|
34 |
| - /** |
35 |
| - * Computes a hash value for a given value. |
36 |
| - * @param v The value to hash |
37 |
| - * @returns The computed hash value |
38 |
| - */ |
39 |
| - private _valHash(v: T): number { |
40 |
| - if (typeof v === 'number') return v | 0; |
41 |
| - if (typeof v === 'string') { |
42 |
| - let h = 0; |
43 |
| - for (let i = 0; i < v.length; i++) { |
44 |
| - h = (h * 31 + v.charCodeAt(i)) | 0; |
45 |
| - } |
46 |
| - return h; |
| 14 | + private _djb2Dual(str: string): { high: number; low: number } { |
| 15 | + let high = 5381; |
| 16 | + let low = 5387; |
| 17 | + |
| 18 | + for (let i = 0; i < str.length; i++) { |
| 19 | + const char = str.charCodeAt(i); |
| 20 | + |
| 21 | + high = ((high << 5) + high + char) | 0; |
| 22 | + low = ((low << 6) + low + char * 37) | 0; |
47 | 23 | }
|
48 |
| - return Object.is(v, null) ? 0 : this._objHash(v); |
49 |
| - } |
50 | 24 |
|
51 |
| - /** |
52 |
| - * Computes a hash value for an object by assigning a unique identifier. |
53 |
| - * @param obj The object to hash |
54 |
| - * @returns The computed hash value |
55 |
| - */ |
56 |
| - private _objHash(obj: any): number { |
57 |
| - return (obj.__hid ??= (Math.random() * 0xffffffff) | 0); |
| 25 | + return { |
| 26 | + high: high >>> 0, |
| 27 | + low: low >>> 0, |
| 28 | + }; |
58 | 29 | }
|
59 | 30 |
|
60 |
| - /** |
61 |
| - * Adds a value to the set and updates the hash. |
62 |
| - * @param v The value to add |
63 |
| - * @returns The HashedSet instance |
64 |
| - */ |
65 |
| - add(v: T): this { |
66 |
| - if (!this.has(v)) { |
67 |
| - this._hash ^= this._valHash(v); |
| 31 | + add(str: string): this { |
| 32 | + if (!this.has(str)) { |
| 33 | + const hash64 = this._djb2Dual(str); |
| 34 | + this._hashHigh ^= hash64.high; |
| 35 | + this._hashLow ^= hash64.low; |
68 | 36 | }
|
69 |
| - return super.add(v); |
| 37 | + return super.add(str); |
70 | 38 | }
|
71 | 39 |
|
72 |
| - /** |
73 |
| - * Removes a value from the set and updates the hash. |
74 |
| - * @param v The value to remove |
75 |
| - * @returns True if the value was removed, false otherwise |
76 |
| - */ |
77 |
| - delete(v: T): boolean { |
78 |
| - if (this.has(v)) { |
79 |
| - this._hash ^= this._valHash(v); |
| 40 | + delete(str: string): boolean { |
| 41 | + if (this.has(str)) { |
| 42 | + const hash64 = this._djb2Dual(str); |
| 43 | + this._hashHigh ^= hash64.high; |
| 44 | + this._hashLow ^= hash64.low; |
80 | 45 | }
|
81 |
| - return super.delete(v); |
| 46 | + return super.delete(str); |
82 | 47 | }
|
83 | 48 |
|
84 |
| - /** |
85 |
| - * Clears all values from the set and resets the hash. |
86 |
| - */ |
87 | 49 | clear(): void {
|
88 |
| - this._hash = 0; |
89 | 50 | super.clear();
|
| 51 | + this._hashHigh = 0; |
| 52 | + this._hashLow = 0; |
| 53 | + } |
| 54 | + |
| 55 | + get hashHigh(): number { |
| 56 | + return this._hashHigh; |
| 57 | + } |
| 58 | + get hashLow(): number { |
| 59 | + return this._hashLow; |
90 | 60 | }
|
91 | 61 |
|
92 |
| - /** |
93 |
| - * Gets the current hash value of the set. |
94 |
| - * Two sets with the same elements will have the same hash value. |
95 |
| - * @returns The hash value as an unsigned 32-bit integer |
96 |
| - */ |
97 |
| - get hash(): number { |
98 |
| - return this._hash >>> 0; |
| 62 | + hasSameHash(other: HashedSet): boolean { |
| 63 | + return this.hashHigh === other.hashHigh && this.hashLow === other.hashLow; |
99 | 64 | }
|
100 | 65 |
|
101 |
| - /** |
102 |
| - * Checks if this set has the same hash as another HashedSet. |
103 |
| - * This is a fast way to check if two sets might be equal. |
104 |
| - * @param other The other HashedSet to compare with |
105 |
| - * @returns True if the hash values are equal |
106 |
| - */ |
107 |
| - hasSameHash(other: HashedSet<T>): boolean { |
108 |
| - return this.hash === other.hash; |
| 66 | + get hash64String(): string { |
| 67 | + return ( |
| 68 | + this.hashHigh.toString(16).padStart(8, '0') + this.hashLow.toString(16).padStart(8, '0') |
| 69 | + ); |
| 70 | + } |
| 71 | + |
| 72 | + fastEquals(other: HashedSet): boolean { |
| 73 | + return this.size === other.size && this.hasSameHash(other); |
| 74 | + } |
| 75 | + |
| 76 | + getHashStats(): { |
| 77 | + size: number; |
| 78 | + hashHigh: string; |
| 79 | + hashLow: string; |
| 80 | + hash64: string; |
| 81 | + entropy: number; |
| 82 | + } { |
| 83 | + let setBits = 0; |
| 84 | + for (let i = 0; i < 32; i++) { |
| 85 | + if (this._hashHigh & (1 << i)) setBits++; |
| 86 | + if (this._hashLow & (1 << i)) setBits++; |
| 87 | + } |
| 88 | + |
| 89 | + return { |
| 90 | + size: this.size, |
| 91 | + hashHigh: this._hashHigh.toString(16).padStart(8, '0'), |
| 92 | + hashLow: this._hashLow.toString(16).padStart(8, '0'), |
| 93 | + hash64: this.hash64String, |
| 94 | + entropy: setBits / 64, |
| 95 | + }; |
109 | 96 | }
|
110 | 97 | }
|
0 commit comments