• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/**
17 * SparseBitVector is a LLVM-interfaces-like data structure designed to efficiently
18 * represent and manipulate large sets of integers where the majority of the elements
19 * are unset (i.e., sparse). It is particularly useful in scenarios where memory efficiency
20 * is critical, and the set of integers contains large gaps between set bits.
21 *
22 * The SparseBitVector is implemented as a collection of SparseBitVectorElement objects,
23 * where each element represents a fixed-size chunk of the bit vector. This allows the
24 * structure to only allocate memory for the portions of the bit vector that contain
25 * set bits, significantly reducing memory usage for sparse data.
26 *
27 * Key Features:
28 * - **Unordered**: We implement it as unordered rather than LLVM's for performance reason.
29 * - **Sparse Storage**: Only stores the indices of set bits, making it memory-efficient
30 *   for sparse datasets.
31 * - **Efficient Operations**: Supports fast bitwise operations such as union and intersection
32 * - **Iterable**: Provides an iterator to traverse all set bits in stored order.
33 * - **Dynamic Resizing**: Automatically adjusts its internal structure as bits are set
34 *   or reset.
35 *
36 * Perforceman VS Array
37 * - **Random Store**         2.5:1
38 * - **Continuous Store**       1:1
39 * - **Random Test**            1:6
40 * - **Continuous Test**        1:1
41 * - **Random Iterator**        4:1
42 * - **Continuous Iterator**    2:1
43 *
44 * The SparseBitVector is parameterized by `ElementSize`, which defines the size of each
45 * chunk (element) in the bit vector and MUST be times of 32. This allows for customization
46 * based on the expected sparsity and performance requirements.
47 *
48 */
49
50export type Word = Uint16Array;
51const BITWORD_SIZE = 16; // bits of a Word
52const DEFAULT_SIZE = 64;
53class SparseBitVectorElement {
54    private ELEMENT_SIZE; // bits of element. Default as 128
55    private BITWORDS_NUM; // number of words
56    private bits: Word;
57
58    constructor(elementSize: number = DEFAULT_SIZE) {
59        this.ELEMENT_SIZE = elementSize;
60        this.BITWORDS_NUM = Math.ceil(this.ELEMENT_SIZE / BITWORD_SIZE);
61        this.bits = new Uint16Array(this.BITWORDS_NUM);
62    }
63
64    word(idx: number): number {
65        return this.bits[idx];
66    }
67
68    clone(): Word {
69        return new Uint16Array(this.bits);
70    }
71
72    get elementSize(): number {
73        return this.ELEMENT_SIZE;
74    }
75
76    get bitWordNum(): number {
77        return this.BITWORDS_NUM;
78    }
79
80    // Check if the element is empty (all bits are zero)
81    isEmpty(): boolean {
82        return this.isZero();
83    }
84
85    // Set a bit at the given index
86    set(bitIdx: number): void {
87        const wordIndex = Math.floor(bitIdx / BITWORD_SIZE);
88        const bitOffset = bitIdx % BITWORD_SIZE;
89        this.bits[wordIndex] |= 1 << bitOffset;
90    }
91
92    setWord(word: Word): void {
93        this.bits = word;
94    }
95
96    // Reset a bit at the given index
97    reset(bitIdx: number): void {
98        const wordIndex = Math.floor(bitIdx / BITWORD_SIZE);
99        const bitOffset = bitIdx % BITWORD_SIZE;
100        this.bits[wordIndex] &= ~(1 << bitOffset);
101    }
102
103    // Test if a bit is set
104    test(bitIdx: number): boolean {
105        const wordIndex = Math.floor(bitIdx / BITWORD_SIZE);
106        const bitOffset = bitIdx % BITWORD_SIZE;
107        return (this.bits[wordIndex] & (1 << bitOffset)) !== 0;
108    }
109
110    // Set if not existing, else return
111    test_and_set(bitIdx: number): boolean {
112        let old = this.test(bitIdx);
113        if (!old) {
114            this.set(bitIdx);
115            return true;
116        }
117        return false;
118    }
119
120    // Count the number of set bits in this element
121    count(): number {
122        let numBits = 0;
123        this.bits.forEach(word => {
124            numBits += this.countBits(word);
125        });
126        return numBits;
127    }
128
129    // Find the index of the first set bit in this element
130    findFirst(): number {
131        for (let i = 0; i < this.bits.length; i++) {
132            if (this.bits[i] !== 0) {
133                return i * BITWORD_SIZE + this.countTrailingZeros(this.bits[i]);
134            }
135        }
136        return -1; // No bits are set
137    }
138
139    // Find the next set bit after the given index
140    findNext(bitIdx: number): number {
141        bitIdx++;
142        let wordIndex = Math.floor(bitIdx / BITWORD_SIZE);
143        let bitOffset = bitIdx % BITWORD_SIZE;
144
145        // Check the current word
146        // Mask off previous bits
147        let word = this.bits[wordIndex] & (~0 << bitOffset);
148        if (word !== 0) {
149            return wordIndex * BITWORD_SIZE + this.countTrailingZeros(word);
150        }
151
152        // Check subsequent words
153        for (let i = wordIndex + 1; i < this.bits.length; i++) {
154            if (this.bits[i] !== 0) {
155                return i * BITWORD_SIZE + this.countTrailingZeros(this.bits[i]);
156            }
157        }
158
159        return -1; // No more bits are set
160    }
161
162    // Comparison
163    equals(rhs: SparseBitVectorElement): boolean {
164        for (let i = 0; i < this.BITWORDS_NUM; i++) {
165            if (this.bits[i] !== rhs.word(i)) {
166                return false;
167            }
168        }
169
170        return true;
171    }
172
173    // Union this element with another element and return true if this one changed
174    unionWith(other: SparseBitVectorElement): boolean {
175        let changed = false;
176        for (let i = 0; i < this.bits.length; i++) {
177            const oldWord = changed ? 0 : this.bits[i];
178            this.bits[i] |= other.bits[i];
179            if (!changed && oldWord !== this.bits[i]) {
180                changed = true;
181            }
182        }
183        return changed;
184    }
185
186    // Intersect this element with another element and return true if this one changed.
187    intersectWith(other: SparseBitVectorElement): boolean {
188        let changed = false;
189        for (let i = 0; i < this.bits.length; i++) {
190            const oldWord = changed ? 0 : this.bits[i];
191            this.bits[i] &= other.bits[i];
192            if (!changed && oldWord !== this.bits[i]) {
193                changed = true;
194            }
195        }
196        return changed;
197    }
198
199    // Subtract another SparseBitVectorElement from this one.
200    subtractWith(rhs: SparseBitVectorElement): boolean {
201        let changed = false;
202        // Perform subtraction: this = this & ~rhs
203        for (let i = 0; i < this.bits.length; i++) {
204            const oldWord = this.bits[i];
205            this.bits[i] &= ~rhs.bits[i];
206
207            // If any bit was changed, mark as changed
208            if (this.bits[i] !== oldWord) {
209                changed = true;
210            }
211        }
212
213        return changed;
214    }
215
216    // Count the number of set bits in a word
217    countBitsV2(word: number): number {
218        let count = 0;
219        while (word !== 0) {
220            word &= word - 1;
221            count++;
222        }
223        return count;
224    }
225
226    // Count the number of set bits in a word
227    countBits(word: number): number {
228        // assume the value is treated as a unsigned integer
229        let v = word;
230
231        // Step 1: Pairwise addition of bits
232        v = v - ((v >> 1) & 0x55555555);
233        // Step 2: Group bits into 4-bit chunks and add
234        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
235        // Step 3: Group bits into 8-bit chunks and add
236        v = (v + (v >> 4)) & 0xf0f0f0f;
237        // Step 4: Multiply by a magic number to sum all 8-bit chunks into the highest byte
238        v = (v * 0x1010101) >> 24;
239
240        return v;
241    }
242
243    isZero(): boolean {
244        for (let i = 0; i < this.BITWORDS_NUM; i++) {
245            if (this.bits[i] !== 0) {
246                return false;
247            }
248        }
249        return true;
250    }
251
252    // Count trailing zeros in a word
253    private countTrailingZeros(word: number): number {
254        if (word === 0) {
255            return BITWORD_SIZE;
256        }
257
258        if ((word & 1) !== 0) {
259            return 0;
260        }
261
262        let zeroBits = 0;
263        let shift = BITWORD_SIZE / 2; // Start with half the bit width
264        let mask = (1 << shift) - 1; // Mask for the lower half
265
266        while (shift > 0) {
267            if ((word & mask) === 0) {
268                word >>= shift;
269                zeroBits |= Number(shift);
270            }
271            shift >>= 1;
272            mask >>= shift;
273        }
274
275        return zeroBits;
276    }
277}
278
279export class SparseBitVector {
280    private ELEMENT_SIZE: number;
281    // Unordered storage of elements.
282    // key is actually the element index (normally it is in element)
283    private elements: Map<number, SparseBitVectorElement> = new Map();
284
285    constructor(elementsSize: number = DEFAULT_SIZE) {
286        this.ELEMENT_SIZE = elementsSize;
287    }
288
289    get elementSize(): number {
290        return this.ELEMENT_SIZE;
291    }
292
293    get elems(): Map<number, SparseBitVectorElement> {
294        return this.elements;
295    }
296
297    // Set a bit at the given index
298    set(bitIdx: number): void {
299        const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE);
300        let element = this.elements.get(elementIndex);
301        if (!element) {
302            element = new SparseBitVectorElement(this.ELEMENT_SIZE);
303            this.elements.set(elementIndex, element);
304        }
305        element.set(bitIdx % this.ELEMENT_SIZE);
306    }
307
308    // Test if a bit is set
309    test(bitIdx: number): boolean {
310        const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE);
311        const element = this.elements.get(elementIndex);
312        return element ? element.test(bitIdx % this.ELEMENT_SIZE) : false;
313    }
314
315    // Set a bit if not existing. Else return
316    testAndSet(bitIdx: number): boolean {
317        let old = this.test(bitIdx);
318        if (!old) {
319            this.set(bitIdx);
320            return true;
321        }
322        return false;
323    }
324
325    // Reset a bit at the given index
326    reset(bitIdx: number): void {
327        const elementIndex = Math.floor(bitIdx / this.ELEMENT_SIZE);
328        let element = this.elements.get(elementIndex);
329        if (element) {
330            element.reset(bitIdx % this.ELEMENT_SIZE);
331            if (element.isEmpty()) {
332                this.elements.delete(elementIndex);
333            }
334        }
335    }
336
337    // Clear all elements
338    clear(): void {
339        this.elements.clear();
340    }
341
342    // Clone, return a deep copied object
343    clone(): SparseBitVector {
344        const newVector = new SparseBitVector(this.elementSize);
345        for (const [idx, element] of this.elements) {
346            const newElement = new SparseBitVectorElement(this.elementSize);
347            newElement.setWord(element.clone());
348
349            newVector.elems.set(idx, newElement);
350        }
351
352        return newVector;
353    }
354
355    // Find the first set bit in the vector
356    findFirst(): number {
357        if (this.elements.size === 0) {
358            return -1;
359        }
360        const firstElement = this.elements.entries().next().value;
361        if (firstElement) {
362            const firstBit = firstElement[1].findFirst();
363            return firstElement[0] * this.ELEMENT_SIZE + firstBit;
364        } else {
365            return -1;
366        }
367    }
368
369    // Count the number of set bits in the vector
370    count(): number {
371        let count = 0;
372        this.elements.forEach((elem, _) => {
373            count += elem.count();
374        });
375        return count;
376    }
377
378    // Check if the vector is empty
379    isEmpty(): boolean {
380        return this.elements.size === 0;
381    }
382
383    [Symbol.iterator](): IterableIterator<number> {
384        let iter = this.elements.entries();
385        let next = iter.next();
386        const elementSize = this.ELEMENT_SIZE;
387        let element = next.value;
388        if (!element) {
389            return {
390                next(): { value: undefined; done: true } {
391                    return { value: undefined, done: true };
392                },
393                [Symbol.iterator](): IterableIterator<number> {
394                    return this; // Make the iterator itself iterable
395                },
396            };
397        }
398        let bitIndex = element[1].findFirst();
399        return {
400            next(): IteratorResult<number> {
401                if (element) {
402                    let v = element[0] * elementSize + bitIndex;
403                    bitIndex = element[1].findNext(bitIndex);
404                    if (bitIndex === -1) {
405                        next = iter.next();
406                        element = next.value;
407                        if (element) {
408                            bitIndex = element[1].findFirst();
409                        }
410                    }
411                    return { value: v, done: false };
412                }
413                return { value: undefined, done: true };
414            },
415            [Symbol.iterator](): IterableIterator<number> {
416                return this; // Make the iterator itself iterable
417            },
418        };
419    }
420
421    /**
422     * Check if this SparseBitVector is equal to another SparseBitVector.
423     */
424    equals(rhs: SparseBitVector): boolean {
425        if (this.ELEMENT_SIZE !== rhs.ELEMENT_SIZE || this.elems.size !== rhs.elems.size) {
426            return false;
427        }
428
429        let rhsElems = rhs.elems;
430        for (let p of this.elements) {
431            let rhsElem = rhsElems.get(p[0]);
432            if (!rhsElem) {
433                return false;
434            }
435            if (!rhsElem.equals(p[1])) {
436                return false;
437            }
438        }
439        return true;
440    }
441
442    /**
443     * Perform a union operation with another SparseBitVector.
444     * Returns True if this vector was changed, false otherwise.
445     */
446    unionWith(rhs: SparseBitVector): boolean {
447        if (this.equals(rhs) || rhs.elems.size === 0) {
448            return false;
449        }
450
451        let changed = false;
452        let newElems = new Map<number, SparseBitVectorElement>();
453        for (let p of rhs.elems) {
454            let elem = this.elements.get(p[0]);
455            if (elem) {
456                changed = elem!.unionWith(p[1]) || changed;
457            } else {
458                newElems.set(p[0], p[1]);
459            }
460        }
461
462        if (newElems.size > 0) {
463            newElems.forEach((v, k) => this.elements.set(k, v));
464            changed = true;
465        }
466
467        return changed;
468    }
469
470    /**
471     * Perform an intersection operation with another SparseBitVector.
472     * Returns True if this vector was changed, false otherwise.
473     */
474    intersectWith(rhs: SparseBitVector): boolean {
475        if (this.equals(rhs) || rhs.elems.size === 0) {
476            return false;
477        }
478
479        let changed = false;
480        // If either vector is empty, the result is empty
481        if (this.elements.size === 0 || rhs.elems.size === 0) {
482            if (this.elements.size > 0) {
483                this.elements = new Map<number, SparseBitVectorElement>();
484                changed = true;
485            }
486            return changed;
487        }
488
489        let needDeleteIdx: Set<number> = new Set();
490        for (let p of this.elems) {
491            let elem = rhs.elems.get(p[0]);
492            if (elem) {
493                changed = p[1].intersectWith(elem) || changed;
494                if (changed && p[1].isZero()) {
495                    needDeleteIdx.add(p[0]);
496                }
497            } else {
498                needDeleteIdx.add(p[0]);
499            }
500        }
501
502        if (needDeleteIdx.size > 0) {
503            needDeleteIdx.forEach(idx => this.elements.delete(idx));
504            changed = true;
505        }
506
507        return changed;
508    }
509
510    /**
511     * Subtract another SparseBitVector from this one.
512     * This operation modifies the current SparseBitVector in place.
513     * Return True if the current SparseBitVector was modified, false otherwise.
514     */
515    subtractWith(rhs: SparseBitVector): boolean {
516        if (this.elementSize !== rhs.elementSize || this.isEmpty() || rhs.isEmpty()) {
517            return false;
518        }
519
520        let needDeleteIdx: Set<number> = new Set();
521        let changed = false;
522        for (const [elementIndex, element] of this.elements) {
523            const rhsElement = rhs.elements.get(elementIndex);
524
525            if (rhsElement) {
526                changed = element.subtractWith(rhsElement) || changed;
527                if (element.isEmpty()) {
528                    needDeleteIdx.add(elementIndex);
529                }
530            }
531        }
532
533        if (needDeleteIdx.size > 0) {
534            needDeleteIdx.forEach(idx => this.elements.delete(idx));
535            changed = true;
536        }
537
538        return changed;
539    }
540
541    toString(): string {
542        let ar = [...this];
543        return ar.toString();
544    }
545}
546